我的疑问 - 关于C#静态构造函数

  关于C#静态构造函数的资料,我找到的都是这样说的:

  因为这个构造函数是属于类的,而不属于任何一个实例,所以这个构造函数只会被执行一次,而且是在创建此类的第一个实例或引用任何静态成员之前,由.NET自动调用。

  1、静态构造函数既没有访问修饰符,也没有参数。   --因为是.NET调用的,所以像public和private等修饰符就没有意义了。

  2、在创建第一个类实例或任何静态成员被引用时,.NET将自动调用静态构造函数来初始化类。   --也就是说我们无法直接调用静态构造函数,也不可能知道静态构造函数何时会被调用。 

  3、一个类只能有一个静态构造函数。

  4、无参数的构造函数可以与静态构造函数共存。   --尽管参数列表相同,但一个属于类,一个属于实例,所以不会冲突。

  5、最多只运行一次。

  6、静态构造函数不可以被继承。

  7、如果没有写静态构造函数,而类中包含带有初始值设定的静态成员,那么编译器会自动生成默认的静态构造函数。

 

  我用下面的代码从测试了下:

    class A
    {
        
public static int X;
        
static A()
        {
            Console.WriteLine(
"A's constructor");
            X 
= B.Y + 1;
        }
    }
    
class B
    {
        
public static int Y = A.X + 1;
        
static B()
        {
            Console.WriteLine(
"B's constructor");
        }
        
static void Main()
        {
            Console.WriteLine(
"X={0}, Y={1}", A.X, B.Y);
        }
    }

  运行结果是:

  A's constructor
  B's constructor
  X=1, Y=2

 

  乍一看,有点晕。在A的静态构造函数中,将B.Y + 1赋给X, 那么必然要先知道B.Y的值。而在B的静态构造函数中,又得先知道A.X。这样会不会死循环啊?实际上是这样的。。

  编译器载入一个类的时候分两个步骤: 一是将类加载到内存堆的某个位置,这个时候所有静态成员的值都是0或者null;二是如果该类定义了静态构造方法,则执行该方法。所以当调用A的静态方法时,= B.Y + 1,实际上编译器已经完成了第一步的动作,而第二步还没有完成,所以B.Y的值仍然是0。接下来的结果也就理所当然了。

 

  然后再看看下面这段代码: (在前一段代码两个静态构造函数中各加了一句话)

    class A
    {
        
public static int X;
        
static A()
        {
            Console.WriteLine(
"A's constructor");
            X 
= B.Y + 1;
            Console.WriteLine(
"A's constructor && A.X={0}, B.Y={1}", A.X, B.Y);          
        }
    }
    
class B
    {
        
public static int Y = A.X + 1;
        
static B()
        {
            Console.WriteLine(
"B's constructor");
            Console.WriteLine(
"B's constructor && A.X={0}, B.Y={1}", A.X, B.Y);
        }
        
static void Main()
        {
            Console.WriteLine(
"X={0}, Y={1}", A.X, B.Y);
        }
    }

  来看看运行结果:

  A's constructor
  A's constructor && A.X=1, B.Y=0
  B's constructor
  B's constructor && A.X=1, B.Y=2
  X=1, Y=2

 

  按照上面提到的第二点,在创建第一个类实例或任何静态成员被引用时静态构造函数被调用。既然这样的话,在A的静态构造函数中,在执行= B.Y + 1 的时候,应该是去到B的静态构造函数中,可是A's constructor && A.X=1, B.Y=0怎么在之前输出了呢?

 

  百思才得其解----要执行 Main 方法,系统在运行类 B 的静态构造函数之前首先要运行 B.Y 的初始值设定项。因为引用了 A.X 的值,所以导致运行 A 的静态构造函数。这样,A 的静态构造函数将继续计算 X 的值,获取 B.Y 的默认值 0,从而 将 A.X 初始化为 1。这样就完成了运行 A 的静态字段初始值设定和静态构造函数的进程,控制返回到 Y 的初始值的计算,即调用B的静态构造函数,计算结果变为 2。

 

  对于之前的假设和疑问,如果把 Main 方法放在另外一个 class 中,就知道了。

    class A
    {
        
public static int X;
        
static A()
        {
            Console.WriteLine(
"A's constructor");
            X 
= B.Y + 1;
            Console.WriteLine(
"A's constructor && A.X={0}, B.Y={1}", A.X, B.Y);          
        }
    }
    
class B
    {
        
public static int Y = A.X + 1;
        
static B()
        {
            Console.WriteLine(
"B's constructor");
            Console.WriteLine(
"B's constructor && A.X={0}, B.Y={1}", A.X, B.Y);
        }
    }
    
class C
    {
        
static void Main()
        {
            Console.WriteLine(A.X);
        }
    }

  这时候的结果就是:

  A's constructor
  B's constructor
  B's constructor && A.X=0, B.Y=1
  A's constructor && A.X=2, B.Y=1
  2

  

  按照刚刚分析得,虽然只输出A.X, 两个构造函数还是都会运行的。要知道A.X的值,第一次使用A的静态变量,首先需要调用A的静态构造函数。同样的道理,在A的静态构造函数中,调用B的静态构造函数。那么B.Y此时就等于0 + 1(A.X初始默认为0)。回到A的静态构造函数,A.X = 1 + 1。于是就得到最后的结果了。

 

  真相大白!!!

posted on 2010-05-22 23:40  lantionzy  阅读(1971)  评论(12编辑  收藏  举报