下面通过一个简单的Console Application演示Type Innitializer的执行顺序。希望大家各抒己见,对于实验的结果给出一个圆满的解释,同时希望读者从中理解到更多关于编译、关于CLR一些被我们忽略的细节。
代码如下,在类Foo中定义了两个static成员:静态字段Field和静态方法GetString,Field通过于Inline的方式通过调用GetString进行初始化。在Main()中仅仅两行代码:Console.WriteLine("Start ...");Foo.GetString("Manually invoke the static GetString() method!");
1: using System;<!--CRLF-->
2: namespace Artech.TypeInitializerDemo<!--CRLF-->
3: { <!--CRLF-->
4: class Program<!--CRLF-->
5: { <!--CRLF-->
6: static void Main()<!--CRLF-->
7: { <!--CRLF-->
8: Console.WriteLine("Start ...");<!--CRLF-->
9: Foo.GetString("Manually invoke the static GetString() method!");<!--CRLF-->
10: } <!--CRLF-->
11: } <!--CRLF-->
12: <!--CRLF-->
13: class Foo<!--CRLF-->
14: { <!--CRLF-->
15: public static string Field = GetString("Initialize the static field!");<!--CRLF-->
16: <!--CRLF-->
17: public static string GetString(string s)<!--CRLF-->
18: { <!--CRLF-->
19: Console.WriteLine(s); <!--CRLF-->
20: return s;<!--CRLF-->
21: } <!--CRLF-->
22: } <!--CRLF-->
23: } <!--CRLF-->
对于结果,我想很多人都能够猜得到,如果在显示调用GetString()之前,需要完成静态成员的初始化,所以最终的输出结果如下图所示:
然后我们在Main()种多加一行代码:string field = Foo.Field; 也就是获取Foo的静态字段:
1: static void Main()<!--CRLF-->
2: { <!--CRLF-->
3: Console.WriteLine("Start ...");<!--CRLF-->
4: Foo.GetString("Manually invoke the static GetString() method!");<!--CRLF-->
5: string field = Foo.Field;<!--CRLF-->
6: } <!--CRLF-->
最终的输出结果就和上面不一样了,静态字段的初始化工作居然提前了(在Console.WriteLine("Start ...");之前执行)
“神奇”的事情还没有结束,如果我们在Foo中加上一个静态构造函数,其中不执行任何的操作:
1: class Foo<!--CRLF-->
2: { <!--CRLF-->
3: public static string Field = GetString("Initialize the static field!");<!--CRLF-->
4: <!--CRLF-->
5: static Foo()<!--CRLF-->
6: { } <!--CRLF-->
7: <!--CRLF-->
8: public static string GetString(string s)<!--CRLF-->
9: { <!--CRLF-->
10: Console.WriteLine(s); <!--CRLF-->
11: return s;<!--CRLF-->
12: } <!--CRLF-->
13: } <!--CRLF-->
再来看看现在执行结果,又和先前的一样的了。

我先不做任何评论(因为我也不太确定我的认识就是正确的),看看大家对此有什么看法。
再添加另一个static constructor的例子,较之上面一个要简单点。在Bar继承自基类Foo,在Foo和Bar均定义了静态构造函数。静态方法DoSomething()定义在Foo中,在Main()中却通过Bar.DoSomething();进行调用。
1: using System;<!--CRLF-->
2: namespace Artech.TypeInitializerDemo<!--CRLF-->
3: { <!--CRLF-->
4: class Program<!--CRLF-->
5: { <!--CRLF-->
6: static void Main()<!--CRLF-->
7: { <!--CRLF-->
8: Bar.DoSomething(); <!--CRLF-->
9: } <!--CRLF-->
10: } <!--CRLF-->
11: <!--CRLF-->
12: public abstract class Foo<!--CRLF-->
13: { <!--CRLF-->
14: static Foo()<!--CRLF-->
15: { <!--CRLF-->
16: Console.WriteLine("static Foo() is invoked");<!--CRLF-->
17: } <!--CRLF-->
18: <!--CRLF-->
19: public static void DoSomething()<!--CRLF-->
20: { <!--CRLF-->
21: Console.WriteLine("Done ...");<!--CRLF-->
22: } <!--CRLF-->
23: } <!--CRLF-->
24: <!--CRLF-->
25: public class Bar : Foo<!--CRLF-->
26: { <!--CRLF-->
27: static Bar()<!--CRLF-->
28: { <!--CRLF-->
29: Console.WriteLine("static Bar() is invoked");<!--CRLF-->
30: } <!--CRLF-->
31: } <!--CRLF-->
32: } <!--CRLF-->
下面是输出结果,可见虽然通过Bar调用了静态方法DoSomething,但是Bar的静态构造函数没有被执行。这个很好理解,因为Something是定义在基类Foo上,Bar.DoSomething()本质上相当于Foo.DoSomething()。所以只会调用Foo的静态构造函数。
个人觉得,这是编译器值得改进的地方,既然静态方法是基于类型的方法,只能通过定义了该静态方法的那个类型进行调用,至于其他的类,哪怕是该类的子类,都不能调用该方法。编译器不应该让这样的代码通过编译。不知道读者的意见如何。
本文通过几个示例探讨了.NET中TypeInitializer(静态构造函数)的执行顺序,并讨论了不同情况下静态成员初始化的行为差异。



2309

被折叠的 条评论
为什么被折叠?



