偶然看到一道面试题,题目如下:
1 public class A 2 { 3 public static int X; 4 static A() 5 { 6 X = B.Y + 1; 7 } 8 } 9 public class B 10 { 11 public static int Y = A.X + 1; 12 static B() 13 { } 14 } 15 class Program 16 { 17 static void Main(string[] args) 18 { 19 Console.WriteLine("X={0},Y={1}", A.X, B.Y); 20 Console.ReadLine(); 21 } 22 }
要求写出结果。
1 public class A
2 { 3 public static int X; 4 static A() 5 { 6 X = B.Y + 1; 7 } 8 } 9 10 11 public class B 12 { 13 public static int Y = A.X+1; 14 B() 15 { } 16 } 17 18 19 class Program 20 { 21 static void Main(string[] args) 22 { 23 Console.WriteLine("X={0},Y={1}", A.X, B.Y); 24 Console.ReadLine(); 25 } 26 } 27 28 我大致一看,似乎还是我发过去的程序,一问之下,原来B的构造函数不再是静态了。B的构造函数体内没有任何代码,我想当然的认为结果还是和原来一样,但运行之后让我大吃一惊,结果居然成了 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(A.num4); 10 Console.ReadLine(); 11 } 12 static Program() 13 { 14 Console.WriteLine(num1); 15 num3++; 16 Console.WriteLine(num3); 17 } 18 }
我在第3,7,13行都设置了断点。在进入到Main函数之前,编译器会先检查所有的静态字段并给予默认值。我们运行程序,可以看到执行到num2=1的时候进入到断点。此时num1,num2,num3都被赋予了默认的值0,单步执行,进入到静态构造函数,接下来是主函数。我们接着考虑有两个类的情况,还是先看代码: 1 class Program 通过单步执行,可以看到首先是Program类的静态字段被赋值,然后进入到Program类的静态构造函数,执行完构造函数,程序并不是进入Main函数,而是先进入到类A,对静态字段赋值,然后才会进入到Main函数之中。至此,我们似乎可以得出结论,在进入到Main函数之前,类的静态字段会先被赋值,并且Main函数所在类的静态字段会先于类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(A.num4);//注意在这里引用到了类A的静态成员 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 A()//注意这里是非静态的构造函数 24 { 25 } 26 } 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(A.num4);//注意这里只引用了类A的成员 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 A() 24 { 25 } 26 } 27 28 class B 29 { 30 public static int num5 = 1; 31 B(){}//注意这里是非静态的构造函数 32 }
通过单步执行,我们可以看到在执行了Program的静态构造函数之后,进入到类A里边对A的静态成员进行赋值,然后直接进入到Main函数,类B中的代码并没有得到执行的机会。从编译器对代码进行优化的角度来看,这很合理,类B并不需要执行,因此单步执行也无法进入到类B中。通过在Main函数中增加Console.WriteLine(B.num5),我们可以通过单步执行进入到类B中;通过在Main函数中调节引用A.num4和B.num5的顺序,我们能够看到先引用哪个类的成员,在单步执行中就会先进入到哪个类中。据此我们可以得出结论:在被引用到类的静态成员按引用的先后顺序初始化之后,程序才进入到Main函数中。在此之前并没有什么让人疑惑之处。接下来我们把上边的代码稍微修改下: class Program 我们根据先前的代码执行过程知道在进入Main函数之前会对引用到的类的静态成员进行初始化。现在我们看到,Main函数引用到的是类B的成员。根据经验,我们可能会认为,程序会先执行到类B里边,在对B的静态成员求值的时候再进入到类A里边。事情是否真的是这样呢?目前只考虑了非静态的构造函数,如果是静态的构造函数,又会得出怎样的结果呢?有心人可以自己进行实际的测试,我也会在下篇给出自己测试所得到的结果以及自己的一点看法。{ public static int num1; public static int num2 = 1; public static int num3; static void Main(string[] args) { Console.WriteLine(num2); Console.WriteLine(B.num5);//这里引用了类B的静态成员 Console.ReadLine(); } static Program() { Console.WriteLine(num1); num3++; Console.WriteLine(num3); } } class A { public static int num4 = 1; A()//注意这里是非静态的构造函数 { } } class B { public static int num5 = A.num4+1;//类B中引用了类A的静态成员 B(){}//注意这里是非静态的构造函数 } 在上篇中留下了一个问题,想来有心的读者已经自行解决问题并且得出了自己的结论。事实上,程序并不是象通常的函数调用,进进入主调函数,然后进入被调函数。在这里,通过单步执行,可以看到程序先进入到类A中进行静态成员的初始化,然后进入到类B中进行B的静态成员的赋值,最后才进入Main函数。我们可以猜测,编译器根据Main函数中所用到的类的先后顺序对类的静态成员进行初始化。在我们的例子中,编译器检查到引用了类B的静态成员,接着继续检查该成员又用到了类A的静态成员,经过优化,编译器先初始化了类A的静态成员,然后是类B,然后回到主函数继续来执行,这符合我们看到的单步执行的顺序。 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 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。 |
|