在学习 Design&Pattern 的 Singleton Pattern 的时候,因为静态初始化是在 .NET 中实现 Singleton 的首选方法.
1 public sealed class Singleton 2 { 3 static readonly Singleton instance = new Singleton(); 4 5 static Singleton() 6 { 7 } 8 9 Singleton() 10 { 11 } 12 13 public static Singleton Instance 14 { 15 get 16 { 17 return instance; 18 } 19 } 20 } 不过我对于静态构造函数还是有点迷惑!它和实例化对象时的构造函数有什么区别,它们的关系又是什么呢?
查看了相关资料后现在对它有了一定的认识了!
静态成员的初始化语句会早于静态构造函数执行,其次静态构造函数是由CLR调用执行的,所以静态构造函数只能是一个,同时不能还有参数。那么静态构造函数相对于成员初始化的一些弊端都不复存在。
对于静态成员何时被初始化,静态构造函数又何时被调用,它们之间的先后顺序又是怎么样的呢?下面我们给出一个例子来说明一下!
1 using System; 2 3 namespace testStatic 4 { 5 class Program 6 { 7 static void Main( string [] args) 8 { 9 Console.WriteLine(A.X.ToString()); // Return: 2 10 Console.WriteLine(B.Y.ToString()); // Return: 1 11 12 Console.Read(); 13 } 14 } 15 16 class A 17 { 18 public static int X = B.Y; 19 20 static A() 21 { 22 ++ X; 23 } 24 } 25 class B 26 { 27 public static int Y = A.X; 28 29 static B() 30 { 31 ++ Y; 32 } 33 } 34 } 说实话我在以前对于 static 的认识的基础上我真的不知道上面的会返回什么值!(做人得诚实,不懂不能装懂!呵呵...).其结果是“2,1”,也就是
A.X的值为2,
B.Y的值为1.
分析此类问题,只要记住三点就行了:
1. 代码的执行顺序,代码在前的先执行;
2. 静态成员初始化语句要先于静态构造函数执行;
3. 静态成员初始化语句与静态构造函数只执行一次。
如果了解这三点,接下来就分析为什么会出现上面的结果。
当调用到第一条语句的时候:
Console.WriteLine(A.X.ToString());
首先是访问A这个类型,那么要对A这个类型的静态成员进行初始化,其次如果有静态构造函数,需要调用它。对于A的静态成员只有“X”,按照上一单元的过程,先给其分配空间,并辅以0来初始化,其次调用其对应的成员初始化语句来初始化这个静态成员。那么它的成员初始化语句是“X = B.Y”,那么需要访问“B.Y”来初始化X这个静态成员。
对于“B.Y”的访问,就是访问B类型,也是和访问A一样,首先对这个类型的静态成员进行初始化,其次如果有静态构造函数,需要调用它。而B的静态成员只有“Y”,先给其分配空间,并辅以0来初始化,其次调用其对应的成员初始化语句来初始化这个静态成员。
那么对于“Y = A.X”成员初始化语句,由于此时不是第一次访问类型A,所以不再进行静态成员初始化和静态构造函数的调用,对于“A.X”的访问是直接访问。此时“A.X”的值为0,那么Y的值也为0;接着执行B的静态构造函数,这样处理后Y的值为1。那么对于A中的成员初始化语句“X = B.Y”,到此就执行完了,此时A类型中的X与B类型中的Y都是一样的,值为1。不过B中的静态成员初始化语句和静态构造函数都执行过了,而A中的静态构造函数还未执行。因此经过A的静态构造函数处理,A的X值为2,这也就是最后显示的结果。
为了再加深一下对 Static 的印象,再个小的测试片断!这下就对 Static 印象更深了~~`
1 using System; 2 using System.Collections.Generic; 3 using System.Text; 4 using System.Threading; 5 6 namespace testStatic 7 { 8 class Program 9 { 10 static void Main( string [] args) 11 { 12 // Console.WriteLine(A.X.ToString()); 13 // Console.WriteLine(B.Y.ToString()); 14 A objA = A.Instance; 15 16 Console.WriteLine(objA.GetStr()); 17 18 Console.Read(); 19 } 20 } 21 22 class A 23 { 24 static A instance = null ; 25 26 string str = " i am A, i am a member of class A " ; 27 28 public static int X = B.Y; 29 30 /// <summary> 31 /// 静态构造函数仅次于静态数据成员被调用. 32 /// </summary> 33 static A() 34 { 35 ++ X; 36 Console.WriteLine( " hello A. I am from static A constructor " ); 37 } 38 private A() 39 { 40 Console.WriteLine( " hello A. I am from private A constructor " ); 41 } 42 43 public string GetStr() 44 { 45 return str; 46 } 47 48 public static A Instance 49 { 50 get 51 { 52 // 这里只有对类进行实例化的时候,非静态的构造函数才会被调用 53 return instance = new A(); 54 } 55 } 56 57 58 } 59 class B 60 { 61 // 静态成页最先被调用 62 public static int Y = A.X; 63 /// <summary> 64 /// 静态构造函数后于静态成员被调用 65 /// </summary> 66 static B() 67 { 68 ++ Y; 69 Console.WriteLine( " hello B . I am from static B constructor " ); 70 } 71 72 73 /// <summary> 74 /// 只有对象被实例化的时候才会调用非静态构造函数 75 /// </summary> 76 private B() 77 { 78 Console.WriteLine( " hello B . I am from private B constructor " ); 79 } 80 } 81 } 输出为:
hello B . I am from static B constructor
hello A. I am from static A constructor
hello A. I am from private A constructor
i am A, i am a member of class A
先看个例子:
1 using System; 2 3 Class A 4 { 5 static int X; 6 static A() 7 { 8 X = B.Y + 1 ; 9 } 10 } 11 12 Class B 13 { 14 public static int Y = A.X + 1 ; 15 static B() {} 16 17 static void Main() 18 { 19 Console.WriteLine( " X={0},Y={1} " ,A.X,B.Y); 20 } 21 22 } 执行结果是X=2,Y=1
这个例子主要考查2个方面,一是static的用法,二是static的初始化顺序。了解了static的初始化顺序和规则,这个问题答答案就很容易理解了。这里涉及到以下三类static对象:static成员,static方法,static构造函数。规则如下:
一个类的static构造函数在给定的应用程序域中仅执行一次。static构造函数由在应用程序域的下列事件的首次发生时触发:
1)该类的实例被创建。
2)任何一个static成员被引用
3)如果类包含执行入口Main方法,此类的static构造函数在Main方法被调用之前执行。
4)如果类包含任何staic成员,则这些static成员在static构造函数之前进行初始化。
5)如果类包含任何static方法,则这些static方法在static构造函数之后进行初始化。
6)对于存在多个static成员,他们的初始化将按照文本顺序进行,不会因为调用顺序而改变。
现在看看上面的应用程序,Class B中有个Main执行入口,所以B首先得到初始化,顺序是static成员Y->static构造函数。在初始化Y时,引用了A.X,编译器又开始初始化Class A(注意这时Class B的初始化并没有完成),顺序也是static成员X->static构造函数。Class A中X在定义的时候没有被赋予初始值(在定义static变量时,尽量赋予初始值),编译器会默认赋予值0(int型)。然后再执行static的构造函数,由于Class B的初始化这时还没有完成,所以B.Y的值在这时被编译器赋予默认值0,所以在A的static的构造函数执行完后,X的值变为1,然后返回B继续完成初始化,得到Y的值为2。最后执行Main,输出A.X和B.Y的值。相反地,如果将B中的Main方法移出放在一个类C中,那执行结果又是什么呢?依据以上的规则,可以很方便的得出答案。
以下这题可以用来说明规则六,有兴趣的朋友可以思考一下答案
class Class1 { private static Class1 obj = new Class1(); public static int counter1; public static int counter2 = 0 ; private Class1() { counter1 ++ ; counter2 ++ ; } public static Class1 getInstance() { return obj; } [STAThread] static void Main( string [] args) { Class1 obj = Class1.getInstance(); Console.WriteLine( " Class1.counter1== " + Class1.counter1); Console.WriteLine( " Class1.counter2== " + Class1.counter2); } }