扯下闲篇先,本来今天预计整理下委托、事件、Lamada的笔记,然后再把单例模式的懒汉、饿汉模式看完。
在看到懒汉的双重加锁设计时,向同桌贩卖了下该设计的优点,结果反被同桌的一个问题难倒了~!
一. 有无静态构造方法,运行结果大不同
问题:请问下面代码的运行结果是什么?
1 using System; 2 3 namespace FirstTest 4 { 5 class A 6 { 7 public static string name = GetName ("Hello World~!"); 8 9 public static string GetName (string str) 10 { 11 Console.WriteLine("初始化静态成员:{0}", str); 12 return str; 13 } 14 } 15 16 class Program 17 { 18 static void Main(string[] args) 19 { 20 Console.WriteLine ("输出第一句话"); 21 string temp = A.name; 22 Console.WriteLine("输出静态成员:{0}", temp); 23 } 24 } 25 }
一开始,我认为是:1)输出第一句话 2)初始化静态成员 3)输出静态成员
运行结果:
结果出乎意料吧?
为什么静态成员的初始化反而先于第一句话执行呢??
正在我百思不得其解的时候,同桌在原代码的基础上稍加改动,又发给了我:
1 using System; 2 3 namespace FirstTest 4 { 5 class A 6 { 7 public static string name = GetName ("Hello World~!"); 8 9 public static string GetName (string str) 10 { 11 Console.WriteLine("初始化静态成员:{0}", str); 12 return str; 13 } 14 15 // 在A类中增加其静态构造方法 16 static A () 17 { 18 } 19 } 20 21 class Program 22 { 23 static void Main(string[] args) 24 { 25 Console.WriteLine ("输出第一句话"); 26 string temp = A.name; 27 Console.WriteLine("输出静态成员:{0}", temp); 28 } 29 } 30 }
这一次,运行结果和我刚刚预期的结果反而一致了。
运行结果:
为什么A类中有无静态构造方法,会对运行结果造成如此影响?
在对度娘上的博客进行各种遍历搜索查询之后,终于捋清了如下思路:
- 前后两段代码的异同点,仅局限于A类中有无静态构造方法。在缺少静态构造方法的情况下(即第一种情况),运行结果“异常”(不符合预期结果)。
- 在缺少静态构造方法的情况下,程序会自动生成默认静态构造方法
由以上两点推测,较为认同如下观点:
- 编译器在编译的时候,会事先分析Main方法中所需要的静态字段
- 如果这些静态字段所在的类有静态构造函数,则只有在初次引用该字段(或初次实例化类)的时候才进行初始化
- 否则,在调用Main方法前,自动生成静态构造方法,对静态字段进行初始化
继续码代码:增加一个class B,在Main方法中分别调用A、B的静态字段,印证观点:
1 using System; 2 3 namespace FirstTest 4 { 5 class A 6 { 7 public static string name = GetName("Hello World~!"); 8 9 public static string GetName(string str) 10 { 11 Console.WriteLine("初始化静态成员:{0}", str); 12 return str; 13 } 14 15 // 在A类中增加其静态构造方法 16 static A() 17 { 18 } 19 } 20 class B 21 { 22 public static string name = GetName("Learning C#"); 23 24 public static string GetName(string str) 25 { 26 Console.WriteLine("初始化静态成员:{0}", str); 27 return str; 28 } 29 30 // B类中缺少静态构造方法,将由系统自动生成 31 } 32 class Program 33 { 34 static void Main(string[] args) 35 { 36 Console.WriteLine("开始执行Main方法"); 37 // 首先引用A类的静态字段 38 string first = A.name; 39 // 其次引用B类的静态字段 40 string second = B.name; 41 } 42 } 43 }
按照观点分析:1)初始化静态成员:Learning C# 2)开始执行Main方法 3)初始化执行静态成员:Hello World~!
运行结果:
与观点分析的预期相符,结题。
参考博文:
《浅谈静态字段与静态构造函数之间的初始化关系以及执行顺序》
作者:Rain
http://www.cnblogs.com/cpcpc/archive/2010/04/16/2123135.html
二. 静态构造方法与静态字段的执行顺序
在查阅上个问题的过程中,发现在MSDN上对静态构造方法的描述中有如下一条内容:
如果类包含任何带有初始值设定项的静态字段,则在执行该类的静态构造函数时,先要按照文本顺序执行那些初始值设定项。
观点主要阐述了以下3点:
- 执行静态构造方法时,先对静态字段进行初始化,然后再调用静态构造方法里面的内容
- 对静态字段进行初始化时,按照静态字段声明的依次顺序进行
- 若静态字段含有初始值含有初始值设定项,则使用设定项。否则对其使用默认值,值类型的默认值为0(其中布尔类型为false),引用类型的默认值为null。
本着打破砂锅问到底的精神,仅在A类中添加如下两行静态字段,在静态构造函数中添加一行输出语句,进行测试:
1 class A 2 { 3 public static string front = GetName("前排强势占座!!!!"); 4 public static string name = GetName("Hello World~!"); 5 public static string back = GetName("后排无聊围观...."); 6 7 public static string GetName(string str) 8 { 9 Console.WriteLine("初始化静态成员:{0}", str); 10 return str; 11 } 12 13 // 在静态构造方法增加输出语句 14 static A() 15 { 16 Console.WriteLine("调用静态构造方法"); 17 } 18 }
按照MSDN的观点:1)初始化静态成员:Learning C# 2)开始执行Main方法 3)初始化执行静态成员:前排强势占座!!!! 4)初始化执行静态成员:Hello World~! 5)初始化执行静态成员:后排无聊围观.... 6)调用静态构造方法
运行结果:
与观点分析的预期相符,新姿势Get√
另外,根据MSDN中的静态构造函数设计中的描述,CLR可以优化对静态字段进行初始值设定项的代码。
三. 静态构造方法与Main方法的执行顺序
同样的,在MSDN中发现如下观点:
如果类中包含用来开始执行的 Main
方法,则该类的静态构造函数将在调用 Main
方法之前执行。
重写一份代码(含有静态构造方法),再次实验下:
1 using System; 2 3 namespace FourthText 4 { 5 class Program 6 { 7 static void Main(string[] args) 8 { 9 Console.WriteLine ("这是最后一个试验了~\\(≧▽≦)/~啦啦啦"); 10 } 11 12 static Program() 13 { 14 Console.WriteLine ("调用静态构造方法"); 15 } 16 } 17 }
运行结果:
与观点分析的预期相符,新姿势Get√
啊啊啊啊O(≧口≦)O,还不能结题~!结合第一、二条知识,再次测试下没有静态构造方法下的情况吧:
1 using System; 2 3 namespace FourthText 4 { 5 class Program 6 { 7 static void Main(string[] args) 8 { 9 Console.WriteLine("真的是最后一次试验了Zzz"); 10 } 11 12 public static string name = GetName("又被骗了(#‵′)凸"); 13 14 public static string GetName(string str) 15 { 16 Console.WriteLine("初始化静态成员:{0}", str); 17 return str; 18 } 19 } 20 }
运行结果: