我想知道为什么这段代码不会以无休止的递归结束。我想这与静态成员自动初始化为默认值有关,但有人可以告诉我“一步一步”'a'如何得到2的值和'b'为1?
public class A
{
public static int a = B.b + 1;
}
public class B
{
public static int b = A.a + 1;
}
static void Main(string[] args)
{
Console.WriteLine("A.a={0}, B.b={1}", A.a, B.b); //A.a=2, B.b=1
Console.Read();
}
答案 0 :(得分:16)
我想:
A.a
,这会导致A
静态初始化程序触发B.b
,导致B
静态初始化程序触发A.a
被查询;类型初始值设定项已激活(但尚未进行任何分配),因此字段(尚未分配)读为0
0
+ 1
为1
,已分配给B.b
< ================== ========= B
cctor并返回A
cctor 1
+ 1
为2
,已分配给A.a
< ================== ========= A
cctor 2
已返回WriteLine
的A.a
WriteLine
)B.b
; cctor已经解雇,所以我们看到1
答案 1 :(得分:12)
静态字段变量初始值设定项 一个类对应于一个序列 在。中执行的作业 它们出现的文本顺序 类声明。如果是静态的 构造函数存在于类中, 执行静态字段 初始化器紧接在之前发生 执行该静态构造函数。 否则,静态字段 初始化程序在执行时执行 依赖于实现的时间 第一次使用静态场 那个班。
请注意最后一点。该规范继续引用您的确切示例作为规范允许排序的情况;所有规范保证是字段初始化器在静态构造函数运行之前以文本顺序完成。 不保证在另一种类型的字段之前或之后初始化一种类型的字段。
例如,允许jit编译器说“嘿,我看到在这个方法中第一次使用类型A和B即将被jitted,让我花一点时间来确保这些类型是装“。允许抖动在此时执行字段初始值设定项,并且可以自行选择先进行A或先进行B.
简而言之:(1)你不能依赖这种行为;它是实现定义的,(2)规范回答了你的确切问题; 考虑在您对语言语义有疑问时阅读规范。
答案 2 :(得分:6)
它与您访问静态属性的顺序有关。 第一个评估是A.a.在评估A.a时,B.b被初始化。由于未完成对a的实际赋值,因此a的值保持为0,因此B.b变为1。 在B.b初始化之后,可以将该值分配给A.a,即1 + 1,因此2
答案 3 :(得分:2)
要加载的第一种类型恰好是A
。所以类型被加载,它的静态成员a
获得它的默认值为零。之后,调用A
的静态构造函数。该构造函数引用了类型B
,因此B
也被加载并且它的静态构造函数被调用。反过来,该构造函数引用了A
类型,但A
已经加载,因此这里没有任何反应,b
得到它的值为零(当前值为a
)加一,这是一个。之后,B
的静态构造函数返回,并计算a
的值。
答案 4 :(得分:2)
有趣的是,当我更改示例代码中的输出顺序时:
Console.WriteLine("B.b={0} A.a={1}", B.b, A.a);
我得到了相反的结果:
B.b=2 A.a=1
所以看起来它与他们被访问的订单有关
因此,考虑到输出可以通过添加其中一个变量的早期使用而改变,似乎这样的递归定义值是A BAD IDEA(TM): - )
答案 5 :(得分:1)
由于A.a在Console.WriteLine中首先被引用,因此首先加载它,这导致B加载值为A.a为0 => B.b = 1 => A.a变为2
反转印刷品并以另一种方式观察它。