我的疑问 - 关于C#静态构造函数
关于C#静态构造函数的资料,我找到的都是这样说的:
因为这个构造函数是属于类的,而不属于任何一个实例,所以这个构造函数只会被执行一次,而且是在创建此类的第一个实例或引用任何静态成员之前,由.NET自动调用。
1、静态构造函数既没有访问修饰符,也没有参数。 --因为是.NET调用的,所以像public和private等修饰符就没有意义了。
2、在创建第一个类实例或任何静态成员被引用时,.NET将自动调用静态构造函数来初始化类。 --也就是说我们无法直接调用静态构造函数,也不可能知道静态构造函数何时会被调用。
3、一个类只能有一个静态构造函数。
4、无参数的构造函数可以与静态构造函数共存。 --尽管参数列表相同,但一个属于类,一个属于实例,所以不会冲突。
5、最多只运行一次。
6、静态构造函数不可以被继承。
7、如果没有写静态构造函数,而类中包含带有初始值设定的静态成员,那么编译器会自动生成默认的静态构造函数。
我用下面的代码从测试了下:
{
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的静态方法时,X = B.Y + 1,实际上编译器已经完成了第一步的动作,而第二步还没有完成,所以B.Y的值仍然是0。接下来的结果也就理所当然了。
然后再看看下面这段代码: (在前一段代码两个静态构造函数中各加了一句话)
{
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的静态构造函数中,在执行X = 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 中,就知道了。
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。于是就得到最后的结果了。
真相大白!!!