浅谈静态字段与静态构造函数之间的初始化关系以及执行顺序(下)

在上篇中留下了一个问题,想来有心的读者已经自行解决问题并且得出了自己的结论。事实上,程序并不是象通常的函数调用,进进入主调函数,然后进入被调函数。在这里,通过单步执行,可以看到程序先进入到类A中进行静态成员的初始化,然后进入到类B中进行B的静态成员的赋值,最后才进入Main函数。我们可以猜测,编译器根据Main函数中所用到的类的先后顺序对类的静态成员进行初始化。在我们的例子中,编译器检查到引用了类B的静态成员,接着继续检查该成员又用到了类A的静态成员,经过优化,编译器先初始化了类A的静态成员,然后是类B,然后回到主函数继续来执行,这符合我们看到的单步执行的顺序。
    然而,在有构造函数的情况下,事情又非如此。还是刚才的例子,我们把类A的构造函数改为静态,代码如下:

  1.  1 class A   
  2.  2     {   
  3.  3         public static int num4 = 1;   
  4.  4         static A()//注意这里是静态的构造函数   
  5.  5         {   
  6.  6         }   
  7.  7     }   
  8.  8    
  9.  9     class B   
  10. 10     {   
  11. 11         public static int num5 = A.num4+1;//类B中引用了类A的静态成员   
  12. 12         B(){}//注意这里是非静态的构造函数   
  13. 13     }  

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的构造函数也改为静态的:

  1.  1 class Program   
  2.  2     {   
  3.  3         public static int num1;   
  4.  4         public static int num2 = 1;   
  5.  5         public static int num3;   
  6.  6         static void Main(string[] args)   
  7.  7         {   
  8.  8             Console.WriteLine(num2);   
  9.  9             Console.WriteLine(B.num5);//这里引用了类B的静态成员   
  10. 10             Console.ReadLine();   
  11. 11         }   
  12. 12         static Program()   
  13. 13         {   
  14. 14             Console.WriteLine(num1);   
  15. 15             num3++;   
  16. 16             Console.WriteLine(num3);   
  17. 17         }   
  18. 18     }   
  19. 19    
  20. 20     class A   
  21. 21     {   
  22. 22         public static int num4 = 1;   
  23. 23         static A()//注意这里是静态的构造函数   
  24. 24         {   
  25. 25         }   
  26. 26     }   
  27. 27    
  28. 28     class B   
  29. 29     {   
  30. 30         public static int num5 = A.num4+1;//类B中引用了类A的静态成员   
  31. 31         static B(){}//注意这里改为了静态的构造函数   
  32. 32     }  

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。
    现在,我们可以做一个最终猜测了:编译器在编译的时候,会事先分析所需要的静态字段,如果这些静态字段所在的类有静态的构造函数,则忽略字段的初始化,否则先进行静态字段的初始化。对类的静态成员初始化的顺序取决于在Main函数中的引用顺序,先引用到的先进行初始化,但如果类的静态成员的初始化依赖于其它类的静态成员,则会先初始化被依赖类的静态成员。而带有静态构造函数的类的静态字段,只有在引用到的时候才进行初始化。
    回过头来考虑最初令人有些迷惑的代码,可以发现已经不再难以理解。第一段代码中A和B都是具有静态构造函数的类,所以是从Main函数中引用到类A的时候开始进入到类A进行初始化,在初始化的过程中又用到了B的静态成员,然后进入到B中。对于第二段代码,由于B是非静态的构造函数,所以会在主函数执行前被初始化,同样由于初始化的过程中用到了类A的静态成员,然后跳转到类A。这样就造成了代码一和代码二运行之后得到不同的结果。

    附:文中所用代码仅为示例之用,并不符合良好代码的规范和要求。尤其使用未经赋值的字段是一个非常不好的编程习惯,请阅读此文的人予以注意。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值