首先让我们认识什么是,当字段被标记为beforefieldinit类型时,该字段初始化可以发生在任何时候任何字段被引用之前。这句话听起了有点别扭,接下来让我们通过具体的例子介绍。
/// <summary> /// Defines a test class. /// </summary> class Test { public static string x = EchoAndReturn("In type initializer"); public static string EchoAndReturn(string s) { Console.WriteLine(s); return s; } }
上面我们定义了一个包含静态字段和方法的类Test,但要注意我们并没有定义静态的构造函数。
图3 Test类的IL代码
class Test { public static string x = EchoAndReturn("In type initializer"); // Defines a parameterless constructor. static Test() { } public static string EchoAndReturn(string s) { Console.WriteLine(s); return s; } }
上面我们给Test类添加一个静态的构造函数。
图4 Test类的IL代码
通过上面Test类的IL代码的区别我们发现,当Test类包含静态字段,而且没有定义静态的构造函数时,该类会被标记为beforefieldinit。
现在也许有人会问:“被标记为beforefieldinit和没有标记的有什么区别呢”?OK现在让我们通过下面的具体例子看一下它们的区别吧!
class Test { public static string x = EchoAndReturn("In type initializer"); static Test() { } public static string EchoAndReturn(string s) { Console.WriteLine(s); return s; } } class Driver { public static void Main() { Console.WriteLine("Starting Main"); // Invoke a static method on Test Test.EchoAndReturn("Echo!"); Console.WriteLine("After echo"); Console.ReadLine(); // The output result: // Starting Main // In type initializer // Echo! // After echo } }
我相信大家都可以得到答案,如果在调用EchoAndReturn()方法之前,需要完成静态成员的初始化,所以最终的输出结果如下:
图5输出结果
接着我们在Main()方法中添加string y = Test.x,如下:
public static void Main() { Console.WriteLine("Starting Main"); // Invoke a static method on Test Test.EchoAndReturn("Echo!"); Console.WriteLine("After echo"); //Reference a static field in Test string y = Test.x; //Use the value just to avoid compiler cleverness if (y != null) { Console.WriteLine("After field access"); } Console.ReadKey(); // The output result: // In type initializer // Starting Main // Echo! // After echo // After field access }
图6 输出结果
通过上面的输出结果,大家可以发现静态字段的初始化跑到了静态方法调用之前,Wo难以想象啊!
最后我们在Test类中添加一个静态构造函数如下:
class Test { public static string x = EchoAndReturn("In type initializer"); static Test() { } public static string EchoAndReturn(string s) { Console.WriteLine(s); return s; } }
图7 输出结果
理论上,type initializer应该发生在”Starting Main”之后和”Echo!”之前”,但这里却出现了不唯一的结果,只有当Test类包含静态构造函数时,才能确保type initializer的初始化发生在”Starting Main”之后和”Echo!”之前”。
所以说要确保type initializer发生在被字段引用时,我们应该给该类添加静态构造函数。