Why doesn't the CLR always call value type constructors

久未见 提交于 2019-11-29 22:12:04

The Microsoft C#4 Spec has changed slightly from previous versions and now more accurately reflects the behaviour that we're seeing here:

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.)

The ECMA Spec and the Microsoft C#3 Spec both have an extra event in that list: "An instance member of the struct type is referenced". So it looks as if C#3 was in contravention of its own spec here. The C#4 Spec has been brought into closer alignment with the actual behaviour of C#3 and 4.

EDIT...

After further investigation, it appears that pretty much all instance member access except direct field access will trigger the static constructor (at least in the current Microsoft implementations of C#3 and 4).

So the current implementations are more closely correlated with the rules given in the ECMA and C#3 specs than those in the C#4 spec: the C#3 rules are implemented correctly when accessing all instance members except fields; the C#4 rules are only implemented correctly for field access.

(The different specs are all in agreement -- and apparently correctly implemented -- when it comes to the rules relating to static member access and explicitly declared constructors.)

From §18.3.10 of the standard (see also The C# programming language book):

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]

So I would agree with you that the last two lines of your program should each trigger the first rule.

After testing, the consensus seems to be that it consistently triggers for methods, properties, events, and indexers. That means it's correct for all explicit instance members except fields. So if Microsoft's C# 4 rules were chosen for the standard, that would make their implementation go from mostly right to mostly wrong.

This is crazy-by-design behavior of "beforefieldinit" attribute in MSIL. It affects C++/CLI too, I filed a bug report where Microsoft very nicely explained why the behavior is the way it is and I pointed out multiple sections in the language standard that didn't agree / need to be updated to describe the actual behavior. But it's not publicly viewable. Anyway, here's the final word on it from Microsoft (discussing a similar situation in C++/CLI):

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.

The same behavior is what you're seeing, although in a different language.

Another interesting sample:

   struct S
    {
        public int x;
        static S()
        {
            Console.WriteLine("static S()");
        }
        public void f() { }
    }

    static void Main() { new S().f(); }

Update: my observation is that unless static state is used, the static constructor will never be touched - something the runtime seems to decide and doesn't apply to reference types. This begs the question if it's a bug left because it has little impact, it's by design, or it's a pending bug.

Update 2: personally, unless you are doing something funky in the constructor, this behaviour from the runtime should never cause a problem. As soon as you access static state, it behaves correctly.

Update3: further to a comment by LukeH, and referencing Matthew Flaschen's answer, implementing and calling your own constructor in the struct also triggers the static constructor to be called. Meaning that in one out of the three scenarios, the behaviour is not what it says on the tin.

I just added a static property to the type and accessed that static property - it called the static constructor. Without the access of the static property, just creating a new instance of the type, the static constructor wasn't called.

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;
        }
    }

A note in this link specifies that static constructors are not called when simply accessing instances:

http://www.jaggersoft.com/pubs/StructsVsClasses.htm#default

Just putting this in as an 'answer' so that I could share what Mr Richter himself wrote about it (does anyone have a link for the latest CLR spec by the way, its easy to get the 2006 edition but finding it a bit harder to get the latest one):

For this kind of stuff, it is usually better to look at the CLR spec than the C# spec. The CLR spec says:

4. If not marked BeforeFieldInit then that type’s initializer method is executed at (i.e., is triggered by):

• first access to any static field of that type, or

• first invocation of any static method of that type or

• first invocation of any instance or virtual method of that type if it is a value type or

• first invocation of any constructor for that type.

Since none of those conditions are satisfied, the static constructor is not invoked. The only tricky parts to note are that “_x” is an instance field not a static field, and constructing an array of structs does not invoke any instance constructors on the array elements.

I would guess that you're creating an ARRAY of your value type. So the new keyword would be used to initialize memory for the array.

Its valid to say

SomeValType i;
i._x = 5;

with no new keyword anywhere, which is essentially what you're doing here. Were SomeValType a reference type, you'd have to initialize each element of your array with

array[i] = new SomeRefType();
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!