静态构造函数在类的一个实例创建前调用.然而,要注意事件发生的重要顺序.静态字段初始化器在静态构造函数体执行前执行.这确保了实例字段在可能由静态构造函数体引用前被适当的初始化.
在访问类型的任何成员前调用类型初始化器(使用静态构造函数语法来实现)是CLR的默认行为.这样,类型初始化器会在任何代码访问类的字段或方法之前,或类的对象创建之前执行.但是你可以将CLR中定义的元数据属性beforefieldinit应用到类中,来稍微放款这个规则。如果没有beforefieldinit特性,CLR要在类的任何成员触及前调用类型初始化器。有了beforefieldinit特性,CLR可以把类型初始化器推迟到第一个静态字段马上要被访问的时候。这意味着如果给类设置了beforefieldinit特性,可以随时调用实例构造函数和方法而不必要求首先执行类型初始化器。但只要有代码试图访问类的静态字段,CLR就要首先调用类型初始化器。记住这一点:beforefieldinit特性给了CLR推迟类型初始化的余地,但CLR还是要在第一个静态字段被访问前初始化类型。
点击(此处)折叠或打开
- using System;
- namespace ConsoleApplication1
- {
- public class A
- {
- static A()
- {
- Console.WriteLine("static A::A()");
- }
- private static int InitX()
- {
- Console.WriteLine("A.InitX()");
- return 1;
- }
- private static int InitY()
- {
- Console.WriteLine("A.InitY()");
- return 2;
- }
- private static int InitA()
- {
- Console.WriteLine("A.InitA()");
- return 3;
- }
- private static int InitB()
- {
- Console.WriteLine("A.InitB()");
- return 4;
- }
- private int y = InitY();
- private int x = InitX();
- private static int a = InitA();
- private static int b = InitB();
- static void Main(string[] args)
- {
- A a = new A();
- }
- }
- }
- 上面的代码执行后的结果为:
- A.InitA();
- A.InitB();
- static A::A();
- A.InitY();
- A.InitX();
静态构造函数抑制了beforefieldinit特性,而该特性会影响对调用该类的时机。C#里面的静态构造函数,也称为类型构造器,类型初始化器,它是私有的。CLR保证一个静态构造函数在每个AppDomain中只执行一次,而且这种执行线程是安全的,所以在静态构造函数中非常适合于单例模式的初始化(初始化静态字段等同于在静态构造函数中初始化,但不完全相同,因为显示定义静态构造函数会抑制beforefieldinit标志)。JIT编译器在编译一个方法时,会查看代码中引用了哪些类型,任何一个类型定义了静态构造函数,JIT编译器都会检查针对当前AppDomain,是否执行了这个静态构造函数。如果类型构造去没有执行,JIT编译器就会在生成的本地代码中添加对静态构造函数的一个调用,否则就不会添加,因为类型已经初始化。同时CLR还保证在执行本地代码中生成的静态构造函代码的线程安全。
根据上面的描述,我们知道JIT 必须决定是否生成类型静态构造函数代码,还须决定何时调用它。具体在何时调用有两种方式:
precise:JIT编译器可以刚好在创建类型的第一个实例之前,或刚好在访问类的一个非继承的字段或成员之前生产这个调用。
beforefieldinit:JIT编译器可以在首次访问一个静态字段或者一个静态/实例方法之前,或者创建类型的第一个实例之前,随便找一个时间生成调用。具体调用时机由CLR决定,它只保证访问成员之前会执行静态构造函数,但可能会提前很早就执行。
简单点说就是beforefieldinit可能会提前调用一个类型的静态构造函数,而precise模式是非要等到用时才调用类型的静态构造函数,它是严格的延迟装载。
beforefieldinit 是首选的(如果没有自定义静态构造函数,默认就是这种方式),因为它使CLR能够自由选择调用静态构造函数的时机,而CLR会尽可能利用这一点来生成运行得更快的代码。比如说在一个循环中调用单例(且包含首次调用),beforefieldinit方式可以让CLR决定在循环之前就调用静态构造函数来优化,而precise模式则只会在循环体中来调用静态构造函数,并在之后的调用会检测静态构造函数是否已被执行的标志位,这样效率稍低一些。在前面使用静态Field的情况下,beforefieldinit 方式下CLR也认为提前执行静态构造函数是更好的选择。
C# 的单例实现,可以利用 precise 延迟调用这一点来延迟对单例对象的构造(饿汗模式),从而带来一丁点的优化,但是在绝大部分情况下这一丁点的优化作用并不大!