类型构造器
除了实例构造器,CLR还支持类型构造器,也称为静态构造器、类构造器或者类型初始化构造器。类型构造器可应用于引用类型和值类型。
实例构造器的作用是设置类型的实例的初始状态。对应地,类型构造器的作用是设置类型的初始状态。
类型默认没有类型构造器
如果定义也只能定义一个,此外类型构造器永远没有参数,以下代码展示了如何在C#中为引用类型和值类型定义一个类型构造器。
internal sealed class SomeRefType{ static SomeRefType(){} } internal struct SomeValType{ static SomeValType(){} }
定义类型构造器类似于定义无参实例构造器,区别在于必须标记为static。
类型构造器是私有的
C#把他们自动标记为private,如果在源代码中显式将类型构造器标记为private或者其他访问修饰符,C#编译器会报错。
之所以必须私有,是为了防止任由开发人员写的代码调用它,对他的调用总是由CLR负责。
类型构造器只执行一次
类型构造器的调用比较麻烦。JIT编译器在编译一个方法时,会查看代码中都引用了哪些类型。
任何一个类型定义了类型构造器,JIT编译器都会检查针对当前AppDomain,是否已经执行了这个类型构造器。
如果构造器从未执行,JIT编译器会在他生成的本机native代码中添加对类型构造器的调用。
如果类型构造器已经执行,JIT编译器就不添加对他的调用,因为他知道类型已经初始化好了。
由于CLR保证一个类型构造器在每个AppDomain中只执行一次,而且这种执行是线程安全的,
所以非常适合在类型构造器中初始化类型需要的任何单实例对象Singleton。
不要在两类型的类型构造器中写相互引用的代码
单线程中的两个类型构造器包含相互引用的代码可能出问题。
假定A的类型构造器包含了引用sB的代码,B的类型构造器包含了A的代码。
在这种情况下,CLR仍然保证每个类型构造器的代码只被执行一次。
但是完全有可能在A的类型构造器还没有完全执行完毕的前提下就开始执行B的类型构造器。
不要在值类型中定义类型构造器
虽然能在值类型中定义类型构造器。但永远不要真的这么做,因为CLR有时不会调用值类型的静态构造器。
使用内联字段初始化语法静态字段
类型构造器中的代码只能访问类型的静态字段,并且他的常规用途就是初始化这些字段。和实例字段一样,C#提供了一个简单的语法来初始化类型的静态字段。
internal sealed class SomeType{ private static Int32 s_x=5; }
值类型允许使用内联字段初始化静态字段
虽然C#不允许值类型为他的实例字段使用内联字段初始化语法,但可以为静态字段使用。换句话说,将前面定义的SomeType类型从class改为struct,那么代码也能通过编译。
生成上述代码时,编译器自动为SomeType生成一个类型构造器,就好像源代码本来是这样写的。
internal sealed class SomeType{ private static Int32 s_x; static SomeType(){ s_x = 5 } }
类型构造器不应调用基类型的类型构造器
这种调用之所以没必要,是因为类型不可能有静态字段是从基类型分享或继承的。
初始化字段的顺序
internal sealed class SomeRefType{ private static Int32 s_x = 5; static SomeType(){ s_x = 10; } }
在这个例子中,C#编译器只生产一个类型构造器方法,他首先将s_x初始化为5,再把它修改成10.
换言之,当C#编译器为类型构造器生成IL代码时,他首先生成的是初始化静态字段所需的代码,然后才会添加你的类型构造器方法中显式包含的代码。