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

本文通过几个实例探讨了C#中类的静态成员初始化顺序及执行流程,揭示了静态构造函数和非静态构造函数在初始化过程中的不同表现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

 

    偶然看到一道面试题,题目如下:
view plaincopy to clipboardprint?
 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     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     }
 
 要求写出结果。
    当然,因为我不是在面试的情况下遇到这题,所以直接在电脑上运行了,结果是: 。回过头来想想,当执行Console.WriteLine("X={0},Y={1}", A.X, B.Y)一句之时,求取A.X的顺序在前,因此程序首先进入到A的静态构造函数中去计算X的值,而X的值依赖于B的静态成员Y,所以程序应该跳转到B中去求取Y的值,而Y=A.X+1,此时不会再一次进入A中去求取X的值,而是使用整型变量的默认值0,这样计算出来Y的值等于1,返回到A的构造函数中继续计算,得到X的结果为2,符合运行之后得到的结果。为证明我的想法,我在程序中设置断点并单步执行,执行的顺序和我想象一致。
    至此,如我一般的菜鸟认为已经没什么问题了。正巧碰到一朋友,此人乃一高手,我将程序发与他,并告知自己所想。朋友看后,很快给我发会一份代码并让我猜测结果,代码如下:
view plaincopy to clipboardprint?
   
 
 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  
 

 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的构造函数体内没有任何代码,我想当然的认为结果还是和原来一样,但运行之后让我大吃一惊,结果居然成了。一个没有任何代码的空构造函数,只是由静态改为非静态,居然会得到完全不同的结果,这真是让我意想不到。看来,在程序的执行过程之上,有一些东西是我所不了解的。还好我懂得“谦受益,满招损”的道理,于是向朋友求教。在朋友的一步步指导之下,终于明白了其中的缘由,整理记录以备遗忘之时查询,也希望如我一般的菜鸟能够从中多少受益。
    先考虑最简单的情况,只有Main函数所在的那一个类。我们都知道,对于控制台应用程序,Main函数是入口函数,那么,在程序进入到Main函数之前都发生了些什么呢?我们通过代码来看一看:

view plaincopy to clipboardprint?
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     } 
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,单步执行,进入到静态构造函数,接下来是主函数。
    我们接着考虑有两个类的情况,还是先看代码:

view plaincopy to clipboardprint?
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     } 
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     }
 
通过单步执行,可以看到首先是Program类的静态字段被赋值,然后进入到Program类的静态构造函数,执行完构造函数,程序并不是进入Main函数,而是先进入到类A,对静态字段赋值,然后才会进入到Main函数之中。至此,我们似乎可以得出结论,在进入到Main函数之前,类的静态字段会先被赋值,并且Main函数所在类的静态字段会先于类A被赋值。那么,再增加一个类会怎样呢?我们看代码:

view plaincopy to clipboardprint?
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     } 
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函数中。
     在此之前并没有什么让人疑惑之处。接下来我们把上边的代码稍微修改下:
view plaincopy to clipboardprint?
class Program  
    {  
        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(){}//注意这里是非静态的构造函数  
    } 
class Program
    {
        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(){}//注意这里是非静态的构造函数
    }
 


我们根据先前的代码执行过程知道在进入Main函数之前会对引用到的类的静态成员进行初始化。现在我们看到,Main函数引用到的是类B的成员。根据经验,我们可能会认为,程序会先执行到类B里边,在对B的静态成员求值的时候再进入到类A里边。事情是否真的是这样呢?目前只考虑了非静态的构造函数,如果是静态的构造函数,又会得出怎样的结果呢?有心人可以自己进行实际的测试,我也会在下篇给出自己测试所得到的结果以及自己的一点看法。

 

本文来自优快云博客,转载请标明出处:http://blog.youkuaiyun.com/niuyongjie/archive/2009/08/23/4475950.aspx

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值