C# 为什么';t CLR始终调用值类型构造函数

C# 为什么';t CLR始终调用值类型构造函数,c#,struct,clr,static-constructor,typeinitializer,C#,Struct,Clr,Static Constructor,Typeinitializer,我有一个关于值类型中的类型构造函数的问题。这个问题的灵感来源于Jeffrey Richter通过C#3rd ed在CLR中写的东西,他说(在第195页-第8章),你永远不应该在值类型中定义类型构造函数,因为有时候CLR不会调用它 因此,例如(实际上是Jeffrey Richters的例子),即使通过查看IL,我也无法理解为什么在以下代码中没有调用类型构造函数: internal struct SomeValType { static SomeValType() {

我有一个关于值类型中的类型构造函数的问题。这个问题的灵感来源于Jeffrey Richter通过C#3rd ed在CLR中写的东西,他说(在第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
    }
}
因此,对类型构造函数应用以下规则,我就是不明白为什么上面的值类型构造函数根本没有被调用

  • 我可以定义一个静态值类型构造函数来设置类型的初始状态
  • 一个类型不能有多个构造函数-没有默认构造函数
  • 类型构造函数是隐式私有的
  • JIT编译器检查类型的类型构造函数是否已在此AppDomain中执行。如果没有,它将调用发送到本机代码中,否则它不会,因为它知道类型已经“初始化”
  • 所以…我就是不明白为什么我看不到这种类型的数组正在被构造

    我最好的猜测可能是:

  • CLR构造类型数组的方式。我本以为在创建第一个项时会调用静态构造函数
  • 构造函数中的代码没有初始化任何静态字段,因此将忽略它。我已经尝试过在构造函数中初始化私有静态字段,但是字段仍然是默认的0值-因此不调用构造函数
  • 或者…由于设置了公共Int32,编译器正在以某种方式优化构造函数调用-但这充其量只是一个模糊的猜测 最佳实践等等,我只是对它超级感兴趣,因为我想自己看看为什么它没有被调用

    编辑:我在下面为我自己的问题添加了一个答案,只是引用了杰弗里·里克特的话

    如果有人有任何想法,那就太棒了。 非常感谢,,
    James

    我猜您正在创建一个值类型的数组。因此,new关键字将用于初始化数组的内存

    可以这么说

    SomeValType i;
    i._x = 5;
    
    在任何地方都没有新的关键字,这基本上就是你在这里做的。如果SomeValType是引用类型,则必须使用

    array[i] = new SomeRefType();
    

    根据本标准第18.3.10条(另见手册):

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

    • 结构的实例成员是 参考
    • 固定成员 结构被引用
    • 函数的显式声明的构造函数 结构被调用
    [注:创建 结构的默认值(§18.3.4) 类型不会触发静态 构造函数。(这方面的一个例子是 图中元素的初始值 数组。)结束注释]

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


    在测试之后,一致的意见似乎是,它始终会触发方法、属性、事件和索引器。这意味着它对于除字段之外的所有显式实例成员都是正确的。因此,如果选择微软的C#4规则作为标准,这将使它们的实现从基本正确变为基本错误。

    更新:我的观察是,除非使用静态,否则将永远不会触及静态构造函数——这似乎是运行时决定的,不适用于引用类型。这就引出了一个问题:它是因为影响很小而留下的一个bug,是设计造成的,还是一个挂起的bug

    更新2:就个人而言,除非您在构造函数中做了一些古怪的事情,否则运行时的这种行为永远不会导致问题。一旦您访问静态,它就会正常工作

    更新3:进一步引用LukeH的评论,并参考Matthew Flaschen的答案,在结构中实现并调用自己的构造函数也会触发调用静态构造函数。这意味着,在三种情况中,有一种情况下,行为与锡上所说的不同

    我只是在类型中添加了一个静态属性,并访问了该静态属性——它称为静态构造函数。如果不访问static属性,只创建该类型的新实例,就不会调用静态构造函数

    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;
            }
        }
    
    内部结构SomeValType
    {
    公共静态int foo=0;
    公共酒吧;
    静态SomeValType()
    {
    Console.WriteLine(“这永远不会显示”);
    }
    }
    静态类程序
    {
    /// 
    ///应用程序的主要入口点。
    /// 
    [状态线程]
    静态void Main()
    {
    //不会命中静态构造函数
    SomeValType v=新的SomeValType();
    v、 bar=1;
    //命中静态构造函数
    SomeValType.foo=3;
    }
    }
    
    此链接中的一个注释指定,仅在访问实例时不调用静态构造函数:


    MSIL中“beforefieldinit”属性的设计行为让人抓狂。它也会影响C++/CLI,我提交了一份错误报告,Microsoft在报告中很好地解释了这种行为的原因,并指出语言标准中有多个部分不同意/需要更新以描述实际行为。但这是不公开的。无论如何,这里是微软关于它的最后一句话(在C++/CLI中讨论类似的情况):

    因为我们正在调用标准 这里是分区I的第8.9.5行 他说:

    如果标记为BeforeFieldInit,则 类型的初始值
       struct S
        {
            public int x;
            static S()
            {
                Console.WriteLine("static S()");
            }
            public void f() { }
        }
    
        static void Main() { new S().f(); }