views:

363

answers:

7

Hi,

I have a question concerning type constructors within a Value type. This question was inspired by something that Jeffrey Richter wrote in CLR via C# 3rd ed, he says (on page 195 - chapter 8) that you should never actually define a type constructor within a value type as there are times when the CLR will not call it.

So, for example (well...Jeffrey Richters example actually), I can't work out, even by looking at the IL, why the type constructor is not being called in the following code:

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

So, applying the following rules for type constructors I just can't see why the value type constructor above is not called at all.

  1. I can define a static value type constructor to set the initial state of the type.
  2. A type can have no more than one constructor - there is no default one.
  3. Type constructors are implicitly private
  4. The JIT compiler checks whether the type's type constructor has already been executed in this AppDomain. If not it emits the call into native code, else it doesn't as it knows the type is already 'initialized'.

So...I just can't work out why I can't see this type array being constructed.

My best guess would be that it could be:

  1. The way that the CLR constructs a type array. I would have thought that the static constructor would be called when the first item was created
  2. The code in the constructor is not initializing any static fields so it is ignored. I have experimented with initializing private static fields within the constructor but the field remains the default 0 value - therefore the constructor is not called.
  3. Or...the compiler is somehow optimizing away the constructor call due to the public Int32 being set - but that is a fuzzy guess at best!!

Best practices etc asside, I am just super intrigued by it as I want to be able to see for myself why it doesn't get called.

EDIT: I added an answer to my own question below, just a quote of what Jeffrey Richter says about it.

If anyone has any ideas then that would be brilliant. Many thanks, James

A: 

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();
Tim Coker
This is about the static constructor, not an instance constructor.
recursive
+10  A: 

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.

Matthew Flaschen
Hmm... I'd expect `a[0]._x = 123;` to trigger it then... +1 for looking up the spec reference, which is what I was about to look at.
Jon Skeet
@Jon, does the field simply not count as an instance member in this case, since accessing an instance field directly cannot behave differently whether the static constructor is called or not?
Dan Bryant
@Dan: I can't see any reason why it *wouldn't* count as an instance member. I think I'd have seen that in the spec... will chase it up though.
Jon Skeet
The standard is clear on that too. §17.2.5: "When a field, method, property, event, indexer, constructor, or finalizer declaration does not include a static modifier, it declares an instance member." This is in the class section, but §18.2 incorporates it for structs, "All kinds of class-member-declarations except finalizer-declaration are also struct-member-declarations. Except for the differences noted in §18.3, the descriptions of class members provided in §17.1.4 through §17.11 apply to struct members as well." I've looked through §18.3, and I don't see anything else relevant.
Matthew Flaschen
initobj doesn't seem to be called for field access, yet it is for a property (invoking the static ctor)
Marc
The relevant section of the C#4 spec has actually removed the "instance member referenced" event from the list. See my answer for details: http://stackoverflow.com/questions/3246078/why-doesnt-the-clr-always-call-value-type-constructors/3246817#3246817
LukeH
+1  A: 

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

Adam
@Adam: Not quite true. The static constructor will also be hit if you add an explicit instance constructor to the type and then call it. It seems that two out of the three events listed in Matthew's answer are correct, only the first one doesn't seem to apply.
LukeH
@LukeH yes I saw his list, I didn't actually try implementing my own constructor but came to the conclusion that item one in that list was not holding true - I'll amend the post.
Adam
A: 

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.

Ben Voigt
No, that's not the same thing. There's a static constructor, which means the type *won't* have the beforefieldinit flag at all.
Jon Skeet
And the behaviour isn't the same, I added a static field just in case the compiler was ignoring the static constructor for lack of static members, and it still ignores the constructor. Its only calling the constructor if you access a static member.
Adam
Hmm, ok. Jon is right that the type doesn't get marked *beforefieldinit*, but it does inherit from `System.ValueType` which IS *beforefieldinit*. That flag shouldn't be inheritable, but when strange things happen you check everything, right? I have no idea how a static member access can fail to trigger type initialization.
Ben Voigt
Accessing a static field DOES cause the type initializer to run. I now see that Adam was commenting that the mere presence of a static member is not enough, it has to be accessed, but that is actually what I quoted "the type’s initializer method is executed at, or sometime before, first access to any static field defined for that type"
Ben Voigt
I was disputing the quote of "Basically, since the code below has absolutely no static fields, the JIT is completely correct in simply not invoking static class constructors.", but either way, its odd behaviour :-)
Adam
Adam: That's the difference between necessary and sufficient. The logic is sound if confusing: The code has no static fields -> The code doesn't access static fields. The code doesn't access static fields -> The type initializer doesn't have to run.
Ben Voigt
@Ben yes true, my first thought was that the compiler had viewed the code and noticed that no static state was being touched and thus just chopped it out. However, this behaviour differs from reference types, static constructors are called as they should be.
Adam
+1  A: 

Another interesting sample:

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

    static void Main() { new S().f(); }
Orsol
+13  A: 

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

LukeH
Wow! Maybe the conclusion should be to NOT use static constructors in structs, since they're so unreliable.
Jordão
Yet, adding `public int Foo { get; set; }` and accessing the property also calls the static constructor. So removing that line doesn't seem to make the spec any more accurate. Good find regardless.
Marc
Hi Luke, brilliant, thanks very much for getting the spec to look through!! I think that answering that nicely!! Thanks Adam, Matthew, Jon and Ben too! Have downloaded the latest edition of the C# 4 spec to dig through too now :-)
jameschinnock
@Marc: Good catch. Also, if you add any method to the type and call it -- for example `public void Test() { Console.WriteLine("Test"); }` -- then this will also trigger the static constructor. So it looks like the compiler/jitter will emit calls to the static constructor for method calls (including the underlying methods used for property access).
LukeH
Has anyone tested on [events](http://msdn.microsoft.com/en-us/library/8627sbea%28v=VS.71%29.aspx) and indexers yet?
Matthew Flaschen
@Matthew: Just tested. accessing an indexer or hooking up an event also triggers the static constructor.
LukeH
@Luke, that means this change would basically take Microsoft's implementation from 80% right to 80% wrong.
Matthew Flaschen
@Matthew: Yep, it seems like pretty much anything *except* default instantiation or direct field access will trigger the static constructor.
LukeH
Apparently, static constructors (on both structs and classes) are triggered by two things: method calls and static field accesses (note that properties are methods). The difference between structs and classes is that structs do not have default constructors, they are just 0-initialized, which is why the static constructor is not called.
Qwertie
@Qwertie, well, classes are supposed to trigger when an instance is created or a static member is referenced. So they're not supposed to trigger on instance methods. Of course, to call an instance method you have to first call an instance constructor. So you may be right that the real implementation triggers on all methods, but there's no way (I know) to test this.
Matthew Flaschen
A: 

Hi all,

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.

jameschinnock