new关键字
使用new关键字来分配对象
为什么要用new来分配对象呢?在我们通过new关键字把引用赋给对象之后,这个应用才会指向内存中的有效类示例。对象必须使用new关键字来分配到内存,如果我们不用new关键字,并且在之后的代码语句中尝试使用类变量的话,就会收到来自编译器的错误。
构造函数
构造函数永远不会有返回值,即使是void,并且它的名字总是和需要构造的类的名字相同。构造函数可以有多个,让它们彼此不同的是构造函数参数的个数和类型。当我们定义了具有相同名字但参数数量和类型不同的方法时,就是重载方法。
this关键字
this关键字可以用来解决当传入参数名字和类型数据字段的名字相同时产生的作用域歧义。如果没有歧义的话,其实也不需要在类访问它自己的数据或成员时使用this关键字。this关键字的另一个用法是使用一项名为构造函数链的技术来设计类。而一个更简洁的方案是让一个接受最多参数个数的构造函数来做“主构造函数”,并且实现必须的验证逻辑。
代码如下:
class Motorcycle
{
public int driveIntensity;
public string driveName;
public Motorcycle(){ }
public Motorcycle(int intensity):this(intensity,""){ }
public Motorcycle(string name):this(0,name){ }
public Motorcycle(int intensity,string name)
{
if(intensity>10)
{ intensity=10; }
driveIntensity=intensity;
driveName=name;
}
.......
}
构造函数的逻辑流程如下:
- 通过调用只有单个int的构造函数来创建对象。
- 构造函数将提供的数据转发给主构造函数,并且提供调用者没有提供的其他初始参数。
- 主构造函数把传入的数据赋值给对象的字段数据。
- 控制返回到最初调用的构造函数,并且执行所有剩余的代码语句。
可选参数
使用可选参数的单个构造函数示例:
public Motorcycle(int intensity=0,string name=" ") { }
static关键字
通过使用static关键字来定义许多静态成员,如果这样的话,这些成员就只能直接从类级别而不是对象引用调用。比如,System.Console,我们没有从对象级别调用WriteLine()方法。简而言之,静态方法被(类设计者)认为是非常普遍的项,并且不需要在调用成员时创建类型的实例。虽然任何类都可以定义静态方法,但他们通常出现在“工具类”中。根据定义,工具类是不维护任何对象级别的状态且并非由new关键字创建的类。因此,工具类会以类级别(即静态)成员公开所有功能。static关键字可用于:类的数据,类的方法,类的属性,构造函数,整个类定义。
谨记:静态数据字段是由所有对象共享的。
如果同种类的所有对象都经常使用某个值,也非常适合使用静态数据。静态构造函数是特殊的构造函数,并且非常适用于初始化在编译时未知的静态数据的值(例如,我们需要从外部文件读取或者生成随机数等)。
- 一个类只可以定义一个静态构造函数。也就是说静态构造函数不能被重载。
- 静态构造函数不允许访问修饰符并且不能接受任何参数。
- 无论创建了多少类型的对象,静态构造函数也只执行一次。
- 运行库创建类实例或调用者首次访问静态成员之前,运行库会调用静态构造函数。
- 静态构造函数的执行先于任何实例级别的构造函数。
如果一个类被定义为静态的,就不能使用new关键字来创建,并且只能包含static关键字标记的成员或字段。(只包含静态功能的类或结构通常称为工具类)
public | 类型或者类型成员 | 公共的项没有限制。公共成员可从对象以及任何派生类访问。公共类型可以从其他外部程序集进行访问。 |
private | 类型成员或者嵌套类型 | 私有项只能由定义它们的类(或结构)进行访问 |
protected | 类型成员或者嵌套类型 | 受保护项可以由定义它们的类及其任意子类使用,但外部类无法通过C#的点操作符访问 |
internal | 类型或者类型成员 | 内部项只能在当前程序集访问。因此,如果我们在.NET类库中定义一组内部类型的话,其它程序集就不能使用它们 |
protected internal | 类型成员或者嵌套类型 | 如果在一个项上组合protected和Internal关键字,项在定义它们的程序集、类以及派生类中可用 |
C#的封装服务
表示对象状态的成员都不应该标记为公共的。
如果在类中定义class Pay{... private int currPay; ...}在类外方法中Pay pay=new Pay(); pay.currPay=10;则会导致编译器错误。如果希望外部世界和私有字段进行交互,传统的做法是定义访问方法(即get方法)和修改方法(即set方法)。
class Pay
{
...
private int currPay;
public int GetPay()
{
return currPay;
}
public int SetPay(string currpay)
{
currPay=currpay;
}
...
}
但是.NET语言还是提倡使用属性来强制数据封装状态数据。首先,理解属性总是映射到“实际的”访问方法和修改方法。因此,类的设计者还是可以在值赋值之前执行任何必要的内部逻辑(比如,对值进行大写转换,过滤值中的不合法字符,检查数字值的边界等)。比如:
class Employee
{
private string empName;
public string Name
{
get { return empName; }
set
{
if(value.Length>15)
{ Console.WriteLine("Error! Name must be less than 16 characters!"); }
else
{ empName=value; }
}
}
注意,属性通过返回值指定了它所封装的数据类型。而且属性在定义时没有使用括号(甚至空括号)。在属性的set作用域中,我们使用了value标记,它用来表示调用者设置属性时传入的值。该标记不是真正的C#关键字,而是上下文关键字。
自动属性
在创建属性封装数据的过程中,大多数C#属性都在set作用域中包含业务逻辑,但有时只需要简单的获取和设置值,而不需要实现任何逻辑。这意味着你能够以如下所示的代码结尾:
public string musicName
{
get { return musicName; }
set { musicName=value; }
}
而使用自动属性语法可以快速创建该属性。例如:public string musicName { get; set; }
与传统的C#属性不同的是,不允许构建只读或只写的自动属性,必须同时支持读写功能。
先创建如下class:
class Car
{ public string PetName { get; set; }
public int Speed { get; set; }
public string Color { get; set; }
public void DisplayStats()
{
Console.WriteLine("Car Name:{0}",PetName);
Console.WriteLine("Speed:{0}",Speed);
Console.WriteLine("Color:{0}",Color);
}
}
class Garage
{ public int NumberOfCars { get; set; }
public Car MyAuto { get; set; }
}
由于C#会为字段数据提供默认值,你能知道NumberOfCars的值(自动设置为0),但当你直接调用MyAuto时,将会在运行时得到“空引用异常”。这是因为没有为后台使用的Car成员变量设置新的对象。由于私有字段的返回字段是在编译时创建的,所以你不能使用C#的字段初始化语法用new关键字直接分配引用类型。这项工作必须在类构造函数内部执行,以确保对象以安全的方式诞生。例如:
class Garage
{ public int NumberOfCars { get; set; }
public Car MyAuto { get; set; }
public Garage()
{
MyAuto=new Car();
NumberOfCars=1;
}
public Garage(Car car,int number)
{
MyAuto=car;
NumberOfCars=number;
}
}
个更新之后就可以像下面这样将Car对象传入Garage对象:
Car c=new Car();
c.PetName="BMW";
c.Speed=55;
c.Color="Red";
c.DisplayStats();
Garage g=new Garage();
g.MyAuto=c;
Console.WriteLine("Number of Cars in garage:{0}",g.NumberOfCars);
Console.WriteLine("Your car is named:{0}",g.MyAuto.PetName);
}
对象初始化
Point point =new Point { X=30,Y=30};
常量数据
public const double PI=3.14;
常量字段是隐式静态的。定义常量时必须为常量指定初始值。
class MyMathClass
{ public const double PI;
public MyMathClass()
{ PI=3.14; }
}
我们将收到编译时错误,有这种限制是因为在编译时必须知道常量的值,构造函数是在运行时调用的。
只读字段
和静态字段不同,只读字段不是隐式静态的。因此如果要从类级别公开PI,就必须显示使用static关键字。
分布类型
由于字段数据、类型属性和构造函数在生成过程中很少变动,而方法却需要经常修改。
例如:
Employee.cs文件中:
partial class Employee
{
// 方法
// 属性
}
Employee.Internal.cs文件:
partial class Employee
{
// 字段数据
// 构造函数
}
小结
本章的宗旨是介绍C#类类型的作用。我们看到,类可以接受任意构造函数来允许对象用户在创建对象时创建状态。本章还演示了几个类设计技术(以及相关关键字)。回忆一下,this关键字可以用于访问当前对象,static关键字可以用于在类(不是对象)级别定义字段和成员,而const关键字(以及readonly修饰符)可以用来定义在赋值初始值之后永远不能改变的数据点。