对于静态字段,由静态构造函数负责。当然,也可以在实例构造函数中为静态成员赋值,但是,当之后更改这个值,再创建一个新的实例时,你会发现值又被实例构造函数改回去了。
所以,通常在静态构造函数中为静态成员赋值 (因为它只会执行一次)。
如果类型没有定义任何构造函数,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 来调用。
不能显式地为结构声明无参数的构造函数。
静态构造函数
静态构造函数又称类型构造函数,是一个特殊的构造函数,它会在这个类型第一次被实例化或引用任何静态成员之前执行,它具有以下特点:
- 静态构造函数既没有访问修饰符,也没有参数。
- 在创建第一个实例或引用任何静态成员之前,将自动调用静态构造函数来初始化类(的类型对象)。静态构造函数只会执行一次。
- 无法直接调用静态构造函数。它的访问修饰符是 private(不需要写明)。在程序中,用户无法控制何时执行静态构造函数。
- 静态构造函数不应该调用基类型的静态构造函数。这是因为类型不可能有从基类型继承的静态字段。
- 只有在类型包括静态字段且在定义时有赋值动作,C# 才会自动生成一个静态构造函数。
静态构造函数的目的是为了安全地给静态成员赋值。你可以显式定义静态构造函数为静态成员赋值。
如果什么都不做,它通过元数据得知这个类型有什么静态成员,并在其中初始化这些静态成员为默认值。
静态构造函数默认是没有的。因为它的功能是确定的(为静态字段赋值),而且只运行一次。
所以,它不需要访问修饰符和参数。如果没有显式定义,而且,没有在代码中为静态成员赋值,则 C# 不会自动生成静态构造函数。
例如,为静态成员赋值会导致 IL 岀现 .cctor:
class ExampleRef
{
private int a;
private string b;
private static string c = "static";
这会令 C# 在 .cctor 中初始化静态成员:
.method private hidebysig specialname rtspecialname static void .cctor () cil managed
{
// Method begins at RVA 0x208f
// Code size 11 (0xb)
.maxstack 8
IL_0000: Idstr "static"
IL_0005: stsfld string TypeFundamentalLab.ExampleRef::c
IL_000a: ret
} // end of method ExampleRef::.cctor
如果去掉赋值语句:c = "static"
,则不会生成静态构造函数。如果一个类型没有静态字段,那么更不需要自动生成静态构造函数了。
当创建第一个实例之前,堆上没有类型对象,所以要调用静态构造函数,引用静态成员之前,堆上也没有类型对象,而静态成员属于类型对象的一部分,所以也要调用静态构造函数。
这两种情况的最终结果,都是堆上最终出现了一个类型对象。因为类型对象只需要建立一次,所以这个静态构造函数也只能运行一次。在这之后,所有的实例对象都指向这个类型对象。
由于 C# 设计成不让外界直接访问静态构造函数,为静态构造函数设置访问修饰符(例如,设置为 public)或传入参数都是没有意义的,因为不可以主动调用。
静态构造函数只负责初始化静态成员,为类型对象的创建而服务,它和类型的实例对象没有关系。
构造函数的执行顺序
如果存在一个父类和继承它的子类,那么:
- 执行静态构造函数(先子类后父类)。在这一步中,如果发现父类或子类的静态构造函数已经执行过,那么就不会再次执行
- 执行实例构造函数(先父类后子类)。在这一步中,如果遭遇 this,则先调用 this 后面指向的实例构造函数的重载,然后再执行自己。
如果父类的实例构造函数是私有的,那么子类构造函数无法通过编译:
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 方法,结果也是一样。因此,这可以证明子类的实例构造函数确实会调用父类的实例构造函数。
原创文章,作者:奋斗,如若转载,请注明出处:https://blog.ytso.com/22367.html