Why doesn't the CLR always call value type constructors
我有一个关于值类型中类型构造函数的问题。这个问题的灵感来源于Jeffrey Richter通过第三版在clr中所写的东西,他说(在第195页第8章)你不应该在一个值类型中定义类型构造函数,因为有时clr不会调用它。
因此,例如(实际上是Jeffrey Richters的例子),我无法计算出为什么在下面的代码中没有调用类型构造函数,即使通过查看IL也是如此:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | internal struct SomeValType { static SomeValType() { Console.WriteLine("This never gets displayed"); } public Int32 _x; } public sealed class Program { static void Main(string[] args) { SomeValType[] a = new SomeValType[10]; a[0]._x = 123; Console.WriteLine(a[0]._x); //Displays 123 } } |
所以,对类型构造函数应用以下规则,我就是不明白为什么上面的值类型构造函数根本没有被调用。
所以…我就是不明白为什么我看不到这个类型数组被构造。
我的最佳猜测是:
最佳实践等辅助,我只是超级感兴趣,因为我想自己能够看到它为什么不被调用。
编辑:我在下面添加了我自己的问题的答案,只是引用杰弗里·里克特的话。
如果有人有什么想法,那就太棒了。多谢,詹姆斯
Microsoft C_4规范与以前的版本略有不同,现在更准确地反映了我们在这里看到的行为:
11.3.10 Static constructors
Static constructors for structs follow
most of the same rules as for classes.
The execution of a static constructor
for a struct type is triggered by the
first of the following events to occur
within an application domain:
- A static member of the struct type is referenced.
- An explicitly declared constructor of the struct type is called.
The creation of default values
(§11.3.4) of struct types does not
trigger the static constructor. (An
example of this is the initial value
of elements in an array.)
ECMA规范和Microsoft C 3规范在该列表中都有一个额外的事件:"引用了结构类型的实例成员"。所以看起来C 3违反了它自己的规范。C 4规范已与C 3和4的实际行为更为一致。
编辑…
经过进一步调查,几乎所有实例成员访问(除了直接字段访问)都会触发静态构造函数(至少在当前的Microsoft C 3和4实现中)。
因此,当前的实现与ECMA和C 3规范中给出的规则比C 4规范中给出的规则更为密切相关:访问除字段之外的所有实例成员时,C 3规则都是正确实现的;C 4规则只对字段访问正确实现。
(当涉及到与静态成员访问和显式声明的构造函数相关的规则时,不同的规范都是一致的——并且显然是正确实现的。)
本标准第18.3.10节(另见C编程语言手册):
The execution of a static constructor for a struct is triggered by the first of the following events to occur within an application domain:
- An instance member of the struct is
referenced.- A static member of
the struct is referenced.- An explicitly declared constructor of the
struct is called.[Note: The creation
of default values (§18.3.4) of struct
types does not trigger the static
constructor. (An example of this is
the initial value of elements in an
array.) end note]
所以我同意你的程序最后两行应该触发第一条规则。
在测试之后,人们的共识似乎是,它总是触发方法、属性、事件和索引器。这意味着它对于除字段之外的所有显式实例成员都是正确的。因此,如果为标准选择了微软的C 4规则,这将使其实现从基本正确到基本错误。
把这个作为一个"答案",这样我就可以分享里克特先生自己写的关于它的文章(顺便问一下,是否有人有一个最新的clr规范的链接,它很容易得到2006年的版本,但发现要得到最新的版本有点困难):
对于这类东西,查看clr规范通常比查看c规范要好。clr规范说:
4。如果未标记beforefieldinit,则该类型的初始值设定项方法将在执行(即,由以下项触发):
?第一次访问该类型的任何静态字段,或
?第一次调用该类型的任何静态方法或
?第一次调用该类型的任何实例或虚拟方法(如果它是值类型)或
?第一次调用该类型的任何构造函数。
由于这些条件都不满足,所以不会调用静态构造函数。唯一需要注意的是,"x"是一个实例字段而不是静态字段,并且构造一个结构数组不会在数组元素上调用任何实例构造函数。
另一个有趣的例子:
1 2 3 4 5 6 7 8 9 10 11 | struct S { public int x; static S() { Console.WriteLine("static S()"); } public void f() { } } static void Main() { new S().f(); } |
这是疯狂的设计行为的"beforefieldinit"属性在msil。它也影响了C++/CLI,我提交了一个bug报告,其中微软很好地解释了为什么行为是这样的,并且我指出了语言标准中不同意/需要更新以描述实际行为的多个部分。但这是不公开的。总之,这里是微软的最后一个字(讨论C++中类似的情况):
Since we're invoking the standard
here, the line from Partition I, 8.9.5
says this:If marked BeforeFieldInit then the
type’s initializer method is executed
at, or sometime before, first access
to any static field defined for that
type.That section actually goes into detail
about how a language implementation
can choose to prevent the behavior
you're describing. C++/CLI chooses not
to, rather they allow the programmer
to do so if they wish.Basically, since the code below has
absolutely no static fields, the JIT
is completely correct in simply not
invoking static class constructors.
同样的行为就是你所看到的,尽管用的是不同的语言。
更新:我的观察是,除非使用静态状态,否则将永远不会接触静态构造函数——运行时似乎决定了这一点,并且不适用于引用类型。这就回避了这样一个问题:它是因为影响很小而留下的一个bug,还是因为设计原因,或者它是一个悬而未决的bug。
更新2:就个人而言,除非您在构造函数中做了一些奇怪的事情,否则运行时的这种行为永远不会引起问题。一旦您访问静态状态,它就会正常工作。
更新3:除了Lukeh的注释之外,参考Matthew Flaschen的答案,在结构中实现和调用自己的构造函数也会触发调用静态构造函数。这意味着,在三种情况中,有一种情况下,行为并不是它在锡上所说的。
我刚向类型添加了一个静态属性,并访问了该静态属性——它称为静态构造函数。如果不访问静态属性,只创建类型的新实例,则不会调用静态构造函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | internal struct SomeValType { public static int foo = 0; public int bar; static SomeValType() { Console.WriteLine("This never gets displayed"); } } static class Program { /// <summary> /// The main entry point for the application. /// </summary> [STAThread] static void Main() { // Doesn't hit static constructor SomeValType v = new SomeValType(); v.bar = 1; // Hits static constructor SomeValType.foo = 3; } } |
此链接中的一个注释指定,仅访问实例时不调用静态构造函数:
http://www.jaggersoft.com/pubs/structsvsclasses.htm默认
我猜您正在创建一个值类型的数组。因此,新的关键字将用于初始化数组的内存。
可以这么说
1 2 | SomeValType i; i._x = 5; |
任何地方都没有新的关键字,这基本上就是你在这里所做的。如果somevaltype是引用类型,则必须使用
1 |