为什么CLR不总是调用值类型构造函数

jam*_*ock 37 c# clr struct static-constructor typeinitializer

我有一个关于Value类型中的类型构造函数的问题.这个问题的灵感来自Jeffrey Richter在CLR中通过C#3rd ed编写的内容,他说(在第195页 - 第8章)你不应该在值类型中实际定义类型构造函数,因为有时候CLR不会调用它.

所以,例如(好吧......实际上是Jeffrey Richters的例子),即使通过查看IL,我也无法解决为什么在以下代码中没有调用类型构造函数:

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
    }
}
Run Code Online (Sandbox Code Playgroud)

因此,对类型构造函数应用以下规则,我只是看不出为什么根本没有调用上面的值类型构造函数.

  1. 我可以定义一个静态值类型构造函数来设置类型的初始状态.
  2. 一个类型只能有一个构造函数 - 没有默认构造函数.
  3. 类型构造函数是隐式私有的
  4. JIT编译器检查是否已在此AppDomain中执行了类型的类型构造函数.如果不是它将调用发送到本机代码,否则它不会,因为它知道该类型已经"初始化".

所以......我无法理解为什么我看不到这个类型的数组正在被构造.

我最好的猜测是它可能是:

  1. CLR构造类型数组的方式.我原以为在创建第一个项目时会调用静态构造函数
  2. 构造函数中的代码不会初始化任何静态字段,因此会被忽略.我已经尝试在构造函数中初始化私有静态字段,但该字段仍然是默认值0 - 因此不会调用构造函数.
  3. 或者......由于公共Int32被设置,编译器以某种方式优化了构造函数调用 - 但这是一个模糊的猜测!

最佳实践等帮助,我只是对它非常感兴趣,因为我希望能够亲眼看到它为什么不被调用.

编辑:我在下面添加了我自己的问题的答案,只是引用杰弗里里希特所说的.

如果有人有任何想法,那将是辉煌的.非常感谢,詹姆斯

Luk*_*keH 17

微软C#4规格略有上升,从以前的版本,现在更准确地改变,反映了我们在这里看到的行为:

11.3.10静态构造函数

结构的静态构造函数遵循与类相同的大多数规则.结构类型的静态构造函数的执行由应用程序域中发生的以下第一个事件触发:

  • 引用了struct类型的静态成员.
  • 调用显式声明的struct类型的构造函数.

结构类型的默认值(第11.3.4节)的创建不会触发静态构造函数.(这方面的一个例子是数组中元素的初始值.)

ECMA规格微软C#3规格都有在该列表中一个额外的事件:"在结构类型的实例成员引用".所以看起来C#3在这里违反了自己的规范.C#4规范已经与C#3和4的实际行为更加一致.

编辑...

在进一步调查之后,几乎所有实例成员访问除了直接字段访问之外都会触发静态构造函数(至少在C#3和4的当前Microsoft实现中).

因此,当前的实现与ECMA和C#3规范中给出的规则更密切相关,而不是C#4规范中的规则:当访问字段之外的所有实例成员时,C#3规则正确实现; C#4规则适用于现场访问.

(当涉及到静态成员访问和显式声明的构造函数的规则时,不同的规范都是一致的 - 并且显然已经正确实现了.)

  • 然而,添加`public int Foo {get; 组; 访问该属性也调用静态构造函数.因此删除该行似乎不会使规范更准确.无论如何都好找. (3认同)

Mat*_*hen 11

从标准的§18.3.10开始(另请参阅C#编程语言书):

结构的静态构造函数的执行由应用程序域中发生的以下第一个事件触发:

  • 引用了结构的实例成员.
  • 引用结构的静态成员.
  • 调用结构的显式声明构造函数.

[ 注意:结构类型的默认值(第18.3.4节)的创建不会触发静态构造函数.(这方面的一个例子是数组中元素的初始值.)end note ]

所以我同意你的观点,你的程序的最后两行应该各自触发第一条规则.

经过测试,共识似乎是它始终触发方法,属性,事件和索引器.这意味着它对除了字段之外的所有显式实例成员都是正确的.因此,如果为标准选择了微软的C#4规则,那么这将使他们的实施从大多数正确到大多数错误.

  • 嗯......我希望`a [0] ._ x = 123;`然后触发它...... +1用于查找规范参考,这就是我要看的内容. (2认同)
  • @Dan:我看不出为什么*不会*算作实例成员的任何理由.我想我已经看到了规范......但是会追逐它. (2认同)