我正在设计一个特定于域的CLI,在过去的几个星期里,我一直在调查各种角落案例,以确保我对所需要的内容有充分的了解。
现在我正在研究类型构造。我正在考虑以下情况,我找不到太多信息:
class C
{
public static int Field = D.Field;
}
class D
{
public static int Field = C.Field;
}
class TestProg
{
static void Main()
{
Console.WriteLine( D.Field );
}
}
这两个类都标记为beforefieldinit
。
有趣的是,这个程序实际编译并运行在MSCLR上,产生:
0
所以似乎在实践中发生的事情是C..cctor
构建D
的触发点被忽略,因为D
的构造已经开始。但是,对我来说,这个程序看起来无效,因为C..cctor
在完全构造之前正在使用 。
许多人会指出上面的场景毫无意义,但作为CLI的实现者,我担心这个问题,因为我需要知道我对类型初始值设定项中的循环引用有多少自由度< / em>的
我在ECMA-335中可以找到的所有内容是:
如果标记为BeforeFieldInit,则类型的初始化方法在首次访问为该类型定义的任何静态字段时或之前执行。
“执行时”这两个词在这种情况下会留下一些含糊之处,因为它们没有指定是否必须执行整个初始化程序或是否必须简单地开始执行。
我发表评论暗示关于循环引用案例的CLI规范中的特定规则,但到目前为止,我还未能在ECMA-335中找到任何关于该问题的提及。
所以,我的问题是:
上述程序是否依赖未定义的行为?或未指明的行为?
如果我的CLR拒绝加载上述程序,它是否仍然符合要求?
如果没有,那么是关于类型构造函数中循环引用的确切规则?
是否有任何有效,有用的设计模式,当流量控制打折时,可能会导致程序类型初始值设定项的有向图中的循环?
答案 0 :(得分:1)
这是一个答案:
II.10.5.3.1 - ECMA 335的II.10.5.3.3对此作出了相当明确的回答,并解释了以下大多数行为。
这是一个非答案,但对评论来说还是很重要。
在我看来这是说明性的:
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine(D.Field);
Console.WriteLine(C.Field);
Console.WriteLine(C.FieldX);
Console.WriteLine(D.FieldY);
Console.ReadLine();
}
}
}
class D
{
public static int Field = C.FieldX;
public static int FieldY = C.Field;
}
class C
{
public static int FieldX = 5;
public static int Field = D.Field;
}
可靠输出: 五 0 五 0
使用参考类型的类似结果。
我相信你的'所以看起来在实践中发生的事情是C..cctor构造D的触发点被忽略,因为D的结构已经开始了。是假的。的实际意图 '如果标记为BeforeFieldInit,则类型的初始化方法在首次访问为该类型定义的任何静态字段时或之前执行。 似乎对我来说,实际上在实践中(在这个例子中)是:
CLR识别Main使用D,想要初始化D,识别D使用C,想要在D的初始化真正启动之前初始化C 并执行。 (见II.10.5.3.3)
即C在D之前被严格初始化,而对D的任何引用都将为null / default。
在我的示例中,交换前两个主线
static void Main(string[] args)
{
Console.WriteLine(C.Field);
Console.WriteLine(D.Field);
D将首先严格初始化,输出
0 0 五 0
现在我的问题是你真正的问题是什么!即你是否有一个更接近你所关心的循环依赖的例子。
另请注意,此处的行为完全类似于
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine(D.Field);
Console.WriteLine(D.FieldY);
Console.ReadLine();
}
}
}
class D
{
public static int Field = FieldY;
public static int FieldY = 5;
}
这是0 5
我觉得这是'相同'的例子,但在单一类型初始化中,即你可以依赖于字段的“存在”而不是初始化,如果你想要更确定的排序,那么写静态构造函数(IIRC在beforefieldinit之前停止)并在II.10.5.3.3中创建更具确定性的方法。