然而,在有构造函数的情况下,事情又非如此。还是刚才的例子,我们把类A的构造函数改为静态,代码如下:
1
class
A
2 {
3 public static int num4 = 1 ;
4 static A() // 注意这里是静态的构造函数
5 {
6 }
7 }
8
9 class B
10 {
11 public static int num5 = A.num4 + 1 ; // 类B中引用了类A的静态成员
12 B(){} // 注意这里是非静态的构造函数
13 }
通过单步执行,我们可以看到,程序先进入到了类B,在对B的静态成员求值的时候才进入了类A,最后进入Main函数。现在,我们再做一点改动,把类B的构造函数也改为静态的:
2 {
3 public static int num4 = 1 ;
4 static A() // 注意这里是静态的构造函数
5 {
6 }
7 }
8
9 class B
10 {
11 public static int num5 = A.num4 + 1 ; // 类B中引用了类A的静态成员
12 B(){} // 注意这里是非静态的构造函数
13 }
1
class
Program
2 {
3 public static int num1;
4 public static int num2 = 1 ;
5 public static int num3;
6 static void Main( string [] args)
7 {
8 Console.WriteLine(num2);
9 Console.WriteLine(B.num5); // 这里引用了类B的静态成员
10 Console.ReadLine();
11 }
12 static Program()
13 {
14 Console.WriteLine(num1);
15 num3 ++ ;
16 Console.WriteLine(num3);
17 }
18 }
19
20 class A
21 {
22 public static int num4 = 1 ;
23 static A() // 注意这里是静态的构造函数
24 {
25 }
26 }
27
28 class B
29 {
30 public static int num5 = A.num4 + 1 ; // 类B中引用了类A的静态成员
31 static B(){} // 注意这里改为了静态的构造函数
32 }
通过单步执行,我们可以看到会先进入到Main函数中,然后进入到类B,然后是类A。
2 {
3 public static int num1;
4 public static int num2 = 1 ;
5 public static int num3;
6 static void Main( string [] args)
7 {
8 Console.WriteLine(num2);
9 Console.WriteLine(B.num5); // 这里引用了类B的静态成员
10 Console.ReadLine();
11 }
12 static Program()
13 {
14 Console.WriteLine(num1);
15 num3 ++ ;
16 Console.WriteLine(num3);
17 }
18 }
19
20 class A
21 {
22 public static int num4 = 1 ;
23 static A() // 注意这里是静态的构造函数
24 {
25 }
26 }
27
28 class B
29 {
30 public static int num5 = A.num4 + 1 ; // 类B中引用了类A的静态成员
31 static B(){} // 注意这里改为了静态的构造函数
32 }
现在,我们可以做一个最终猜测了:编译器在编译的时候,会事先分析所需要的静态字段,如果这些静态字段所在的类有静态的构造函数,则忽略字段的初始化,否则先进行静态字段的初始化。对类的静态成员初始化的顺序取决于在Main函数中的引用顺序,先引用到的先进行初始化,但如果类的静态成员的初始化依赖于其它类的静态成员,则会先初始化被依赖类的静态成员。而带有静态构造函数的类的静态字段,只有在引用到的时候才进行初始化。
回过头来考虑最初令人有些迷惑的代码,可以发现已经不再难以理解。第一段代码中A和B都是具有静态构造函数的类,所以是从Main函数中引用到类A的时候开始进入到类A进行初始化,在初始化的过程中又用到了B的静态成员,然后进入到B中。对于第二段代码,由于B是非静态的构造函数,所以会在主函数执行前被初始化,同样由于初始化的过程中用到了类A的静态成员,然后跳转到类A。这样就造成了代码一和代码二运行之后得到不同的结果。
附:文中所用代码仅为示例之用,并不符合良好代码的规范和要求。尤其使用未经赋值的字段是一个非常不好的编程习惯,请阅读此文的人予以注意。