3. 构造函数
在C#中声明基本构造函数的语法与在Java 和C++中相同。下面声明一个与包含的类同名的方法,但该方法没有返回类型:
public class MyClass
{
public MyClass()
{
}
// rest of class definition
与Java 和 C++相同,没有必要给类提供构造函数,在我们的例子中没有提供这样的构造函数。一般情况下,如果没有提供任何构造函数,编译器会在后台创建一个默认的构造函数。这是一个非常基本的构造函数,它只能把所有的成员字段初始化为标准的默认值(例如,引用类型为空引用,数字数据类型为0,bool为false)。这通常就足够了,否则就需要编写自己的构造函数。
注意:
对于C++程序员来说,C#中的基本字段在默认情况下初始化为0,而C++中的基本字段不进行初始化,不需要像C++那样频繁地在C#中编写构造函数。
构造函数的重载遵循与其他方法相同的规则。换言之,可以为构造函数提供任意多的重载,只要它们的签名有明显的区别即可:
public MyClass() // zero-parameter constructor
{
// construction code
}
public MyClass(int number) // another overload
{
// construction code
}
但注意,如果提供了带参数的构造函数,编译器就不会自动提供默认的构造函数,只有在没有定义任何构造函数时,编译器才会自动提供默认的构造函数。在下面的例子中,因为定义了一个带一个参数的构造函数,所以编译器会假定这是可以使用的惟一构造函数,不会隐式地提供其他构造函数:
public class MyNumber
{
private int number;
public MyNumber(int number)
{
this.number = number;
}
}
上面的代码还说明,一般使用this关键字区分成员字段和同名的参数。如果试图使用无参数的构造函数实例化MyNumber对象,就会得到一个编译错误:
MyNumber numb = new MyNumber(); // causes compilation error
注意,可以把构造函数定义为private或protected,这样不相关的类就不能访问它们:
public class MyNumber
{
private int number;
private MyNumber(int number) // another overload
{
this.number = number;
}
}
在这个例子中,我们并没有为MyNumber定义任何公共或受保护的构造函数。这就使MyNumber不能使用new运算符在外部代码中实例化(但可以在MyNumber上编写一个公共静态属性或方法,以进行实例化)。这在下面两种情况下是有用的:
● 类仅用作某些静态成员或属性的容器,因此永远不会实例化。
● 希望类仅通过调用某个静态成员函数来实例化(这就是所谓对象实例化的类代理方法)
(1) 静态构造函数
C#的一个新特征是也可以给类编写无参数的静态构造函数。这种构造函数只执行一次,而前面的构造函数是实例构造函数,只要创建类的对象,它都会执行。静态构造函数在C++和VB6中没有对应的函数。
class MyClass
{
static MyClass()
{
// initialization code
}
// rest of class definition
}
编写静态构造函数的一个原因是,类有一些静态字段或属性,需要在第一次使用类之前,从外部源中初始化这些静态字段和属性。
.NET运行库没有确保静态构造函数什么时候执行,所以不要把代码放在某个特定的时刻(例如,加载程序集时)执行的静态构造函数中。也不能预计不同类的静态构造函数按照什么顺序执行。但是,可以确保静态构造函数至多运行一次,即在代码引用类之前执行。在C#中,静态构造函数通常在第一次调用类的成员之前执行。
注意,静态构造函数没有访问修饰符,其他C#代码从来不调用它,但在加载类时,总是由.NET运行库调用它,所以像public和private这样的访问修饰符就没有意义了。同样,静态构造函数不能带任何参数,一个类也只能有一个静态构造函数。很显然,静态构造函数只能访问类的静态成员,不能访问实例成员。
注意,无参数的实例构造函数可以在类中与静态构造函数安全共存。尽管参数列表是相同的,但这并不矛盾,因为静态构造函数是在加载类时执行,而实例构造函数是在创建实例时执行,所以构造函数的执行不会有冲突。
如果多个类都有静态构造函数,先执行哪个静态构造函数是不确定的。此时应根据其他静态构造函数的执行情况,在静态构造函数中添加代码。另一方面,如果静态字段有默认值,它们就在调用静态构造函数之前指定。
下面用一个例子来说明静态构造函数的用法。假定这个例子叫StaticConstructorSample,基于包含用户设置的程序(假定存储在某个配置文件中)。为了简单一些,假定只有一个用户设置—— BackColor,表示要在应用程序中使用的背景色。因为这里不想编写从外部数据源中读取数据的代码,所以假定该设置在工作日的背景色是红色,在周末的背景色是绿色。程序仅在控制台窗口中显示设置—— 但这足以说明静态构造函数是如何工作的。
namespace Wrox.ProCSharp.StaticConstructorSample
{
public class UserPreferences
{
public static readonly Color BackColor;
static UserPreferences()
{
DateTime now = DateTime.Now;
if (now.DayOfWeek == DayOfWeek.Saturday
|| now.DayOfWeek == DayOfWeek.Sunday)
BackColor = Color.Green;
else
BackColor = Color.Red;
}
private UserPreferences()
{
}
}
}
这段代码说明了颜色设置如何存储在静态变量中,该静态变量在静态构造函数中进行初始化。把这个字段声明为只读类型,表示其值只能在构造函数中设置。本章后面将详细介绍只读字段。这段代码使用了Microsoft在Framework类库中支持的两个有用的结构System.DateTime和System.Drawing.Color。DateTime结构实现了静态属性Now和实例属性DayOfWeek,Now属性返回当前的时间,DayOfWeek属性可以计算出某个日期是星期几。Color(详见第25章)用于存储颜色,它实现了各种静态属性,例如本例使用的Red和Green,返回常用的颜色。为了使用Color结构,需要在编译时引用System.Drawing.dll程序集,且必须为System.Drawing命名空间添加一个using语句:
using System;
using System.Drawing;
用下面的代码测试静态构造函数:
class MainEntryPoint
{
static void Main(string[] args)
{
Console.WriteLine("User-preferences: BackColor is: " +
UserPreferences.BackColor.ToString());
}
}
编译并运行这段代码,会得到如下结果:
C:>StaticConstructor
User-preferences: BackColor is: Color [Red]
(2) 从其他构造函数中调用构造函数
有时,在一个类中有几个构造函数,以容纳某些可选参数,这些构造函数都包含一些共同的代码。例如,下面的情况:
class Car
{
private string description;
private uint nWheels;
public Car(string model, uint nWheels)
{
this.description = description;
this.nWheels = nWheels;
}
public Car(string model)
{
this.description = description;
this.nWheels = 4;
}
// etc.
这两个构造函数初始化了相同的字段,显然,最好把所有的代码放在一个地方。C#有一个特殊的语法,称为构造函数初始化器,可以实现此目的:
class Car
{
private string description;
private uint nWheels;
public Car(string model, uint nWheels)
{
this.description = description;
this.nWheels = nWheels;
}
public Car(string model) : this(model, 4)
{
}
// etc
这里,this关键字仅调用参数最匹配的那个构造函数。注意,构造函数初始化器在构造函数之前执行。现在假定运行下面的代码:
Car myCar = new Car("Proton Persona");
在本例中,在带一个参数的构造函数执行之前,先执行带2个参数的构造函数(但在本例中,因为带一个参数的构造函数没有代码,所以没有区别)。
C#构造函数初始化符可以包含对同一个类的另一个构造函数的调用(使用前面介绍的语法),也可以包含对直接基类的构造函数的调用(使用相同的语法,但应使用base关键字代替this)。初始化符中不能有多于一个的调用。
在C#中,构造函数初始化符的语法类似于C++中的构造函数初始化列表,但C++开发人员要注意,除了语法类似之外,C#初始化符所包含的代码遵循完全不同的规则。可以使用C++初始化列表指定成员变量的初始值,或调用基类构造函数,而C#初始化符中的代码只能调用另一个构造函数。这就要求C#类在构造时遵循严格的顺序,但C++就没有这个要求。这个问题详见第4章,那时就会看到,C#强制遵循的顺序只不过是良好的编程习惯而已。






