引用类型的实例构造函数不能被继承(不过,可以通过 base 关键字调用父类的构造函数),它负责初始化类型的实例字段。
对于静态字段,由静态构造函数负责。当然,也可以在实例构造函数中为静态成员赋值,但是,当之后更改这个值,再创建一个新的实例时,你会发现值又被实例构造函数改回去了。
所以,通常在静态构造函数中为静态成员赋值 (因为它只会执行一次)。
如果类型没有定义任何构造函数,C# 会生成一个无参实例构造函数(.ctor),它遍历类 型中所有的成员,并将它们设置为默认值。
可以声明多个不同的构造函数。可以利用 this 关键字来调用其他构造函数。例如:
public class AClass
{
public int i;
public static int j;
static AClass()
{
j = 2;
Console.WriteLine("静态构造函数");
}
public AClass() : this(5)
{
Console.WriteLine("实例构造函数");
}
public AClass(int i)
{
this.i = i;
Console.WriteLine("有参数的实例构造函数");
}
}
在这个例子中,我们使用下面的代码新建实例:
static void Main(string[] args)
{
var a = new AClass();
Console.WriteLine(a.i);
var b = new AClass(10);
Console.WriteLine(b.i);
Console.ReadKey();
}
根据规则,创建 a 时,调用无参的构造函数。但无参的构造函数调用了有参的构造函数,所以,先执行有参的构造函数,将 i 设置为 5,再执行无参的构造函数。
创建 b 则只调用有参的构造函数。最后的输出结果如下图所示。
如果没有为结构定义构造函数,则 CLR 会生成一个默认的,将其成员初始化为默认值。
如果你自定义一个结构体的构造函数,那么必须初始化它的所有成员。结构的自定义构造函数不会被自动调用,所以你要手动地通过 this 来调用。
不能显式地为结构声明无参数的构造函数。
静态构造函数又称类型构造函数,是一个特殊的构造函数,它会在这个类型第一次被实例化或引用任何静态成员之前执行,它具有以下特点:
静态构造函数的目的是为了安全地给静态成员赋值。你可以显式定义静态构造函数为静态成员赋值。
如果什么都不做,它通过元数据得知这个类型有什么静态成员,并在其中初始化这些静态成员为默认值。
静态构造函数默认是没有的。因为它的功能是确定的(为静态字段赋值),而且只运行一次。
所以,它不需要访问修饰符和参数。如果没有显式定义,而且,没有在代码中为静态成员赋值,则 C# 不会自动生成静态构造函数。
例如,为静态成员赋值会导致 IL 岀现 .cctor:
这会令 C# 在 .cctor 中初始化静态成员:
如果去掉赋值语句:c = "static",则不会生成静态构造函数。如果一个类型没有静态字段,那么更不需要自动生成静态构造函数了。
当创建第一个实例之前,堆上没有类型对象,所以要调用静态构造函数,引用静态成员之前,堆上也没有类型对象,而静态成员属于类型对象的一部分,所以也要调用静态构造函数。
这两种情况的最终结果,都是堆上最终出现了一个类型对象。因为类型对象只需要建立一次,所以这个静态构造函数也只能运行一次。在这之后,所有的实例对象都指向这个类型对象。
由于 C# 设计成不让外界直接访问静态构造函数,为静态构造函数设置访问修饰符(例如,设置为 public)或传入参数都是没有意义的,因为不可以主动调用。
静态构造函数只负责初始化静态成员,为类型对象的创建而服务,它和类型的实例对象没有关系。
如果存在一个父类和继承它的子类,那么:
如果父类的实例构造函数是私有的,那么子类构造函数无法通过编译:
class Father
{
public static int i;
static Father()
{
i = 1;
Console.WriteLine("父类型的静态构造函数");
}
Father()
{
Console.WriteLine("父类型的实例构造函数");
}
}
class Son : Father
{
public static int j;
static Son()
{
j = 1;
Console.WriteLine("子类型的静态构造函数");
}
Son()
{
Console.WriteLine("子类型的实例构造函数");
}
}
上面的代码中父类 Father 的实例构造函数没有访问修饰符,因此,默认会使用 private。
子类的实例构造函数便无法访问父类的实例构造函数,不能通过编译,如下图所示。
即使去掉显式定义的 Son 方法,结果也是一样。因此,这可以证明子类的实例构造函数确实会调用父类的实例构造函数。