代码之家  ›  专栏  ›  技术社区  ›  jameschinnock

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

  •  36
  • jameschinnock  · 技术社区  · 15 年前

    我有个问题 值类型中的类型构造函数 . 这个问题的灵感来源于Jeffrey Richter通过第三版在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
        }
    }
    

    所以,对类型构造函数应用以下规则,我就是不明白为什么上面的值类型构造函数根本没有被调用。

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

    所以…我就是不明白为什么我看不到这个类型数组被构造。

    我的最佳猜测是:

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

    最佳实践等辅助,我只是超级感兴趣,因为我想自己能够看到它为什么不被调用。

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

    如果有人有什么想法,那就太棒了。 多谢, 詹姆斯

    7 回复  |  直到 15 年前
        1
  •  17
  •   LukeH    15 年前

    这个 Microsoft C#4 Spec 与以前的版本略有不同,现在更准确地反映了我们在这里看到的行为:

    11.3.10静态施工人员

    结构的静态构造函数如下 大多数规则与类相同。 静态构造函数的执行 对于结构,类型由 以下事件中的第一个发生 在应用程序域中:

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

    创建默认值 结构类型的(_§11.3.4)不 触发静态构造函数。(安) 例如初始值 数组中的元素。)

    这个 ECMA Spec 以及 Microsoft C#3 Spec 两者都在该列表中有一个额外的事件:“引用了结构类型的实例成员”。 所以看起来C 3违反了它自己的规范。C 4规范与C 3和C 4的实际行为更为一致。

    编辑…

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

    因此,当前的实现与ECMA和C 3规范中给出的规则比C 4规范中给出的规则更为密切相关:访问所有实例成员时,C 3规则是正确实现的。 除了 字段;C 4规则是 只有 正确实施现场访问。

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

        2
  •  11
  •   Matthew Flaschen    15 年前

    来自本标准第18.3.10节(另见 The C# programming language 书):

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

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

    [ 注释 创作 结构的默认值(_§18.3.4) 类型不会触发静态 构造函数。(例如 元素的初始值 数组) 期末笔记 ]

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

    在测试之后,人们的共识似乎是,它总是触发方法、属性、事件和索引器。这意味着它对所有显式实例成员都是正确的 除了 领域。因此,如果为标准选择了微软的C 4规则,这将使其实现从基本正确到基本错误。

        3
  •  1
  •   Ben Voigt    15 年前

    这是疯狂的设计行为的“beforefieldinit”属性在msil。它也影响了C++/CLI,我提交了一个bug报告,其中微软很好地解释了为什么行为是这样的,并且我指出了语言标准中不同意/需要更新以描述实际行为的多个部分。但这是不公开的。总之,这里是微软的最后一个字(讨论C++中类似的情况):

    因为我们在调用标准 这里,分区一的线,8.9.5 这样说:

    如果标记为beforefieldinit,则 执行类型的初始值设定项方法 在第一次访问时或之前的某个时间 到为此定义的任何静态字段 类型。

    那部分实际上是详细的 关于如何实现语言 可以选择阻止行为 你在描述。C++/CLI不选择 相反,他们允许程序员 如果他们愿意的话。

    基本上,因为下面的代码 绝对没有静态字段,JIT 完全正确 调用静态类构造函数。

    同样的行为就是你所看到的,尽管用的是不同的语言。

        4
  •  1
  •   Denis Palnitsky    15 年前

    另一个有趣的例子:

       struct S
        {
            public int x;
            static S()
            {
                Console.WriteLine("static S()");
            }
            public void f() { }
        }
    
        static void Main() { new S().f(); }
    
        5
  •  1
  •   Adam Houldsworth    15 年前

    更新: 我的观察是,除非使用静态状态,否则将永远不会接触静态构造函数——运行时似乎决定了这一点,并且不适用于引用类型。这就回避了这样一个问题:它是因为影响很小而留下的一个bug,还是因为设计原因,或者它是一个悬而未决的bug。

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

    更新3: 除了Lukeh的注释之外,参考Matthew Flaschen的答案,在结构中实现和调用自己的构造函数也会触发调用静态构造函数。这意味着,在三种情况中,有一种情况下,行为并不是它在锡上所说的。

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

    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#default

        6
  •  1
  •   jameschinnock    15 年前

    把这个作为一个“答案”,这样我就可以分享里克特先生自己写的关于它的文章(顺便问一下,是否有人有一个最新的clr规范的链接,它很容易获得2006年的版本,但是发现要获得最新的版本有点困难):

    对于这类东西,查看clr规范通常比查看c规范要好。clr规范说:

    4。如果未标记beforefieldinit,则该类型的初始化器方法将在执行(即,由以下触发):

    _窆首次访问该类型的任何静态字段,或

    _窆首次调用该类型的任何静态方法或

    _窆,如果是值类型或

    _窆首次调用该类型的任何构造函数。

    由于这些条件都不满足,静态构造函数是 调用。唯一需要注意的是,__u x__是一个实例字段,而不是静态字段,构造一个结构数组可以 对数组元素调用任何实例构造函数。

        7
  •  0
  •   Tim Coker    15 年前

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

    可以这么说

    SomeValType i;
    i._x = 5;
    

    任何地方都没有新的关键字,这基本上就是你在这里所做的。如果somevaltype是引用类型,则必须使用

    array[i] = new SomeRefType();
    
    推荐文章