2

C# Specifications reads that

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.

In the following code the static constructor is called despite none of the aforementioned events occured.

struct Notebook
{
    static Notebook() => Console.WriteLine("Static Ctor");

    public void Method() => Console.WriteLine("Method");
}



class Program
    {
        static void Main()
        {
            Notebook notebook;

            notebook.Method();
        }
    }

The same applies when an auto-property declared in a struct is assigned a value in void Main(). What actually makes the static constructor to be called in this case?

  • 2
    From [here](https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/static-constructors): *[A static constructor] is called automatically before the first instance is created or any static members are referenced.* – Robert Harvey Jan 27 '20 at 20:51
  • @RobertHarvey on other hand [lang spec:structs](https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/language-specification/structs#struct-members): "The creation of default values (Default values) of struct types does not trigger the static constructor. (An example of this is the initial value of elements in an array.)" Looks like one need to dig out latest non-draft spec and carefully read it to confirm that `.Method` is the one that actually triggers it (even if it is not `static`)… likely is the case as there is no annotations that says "method doesn't call static methods") – Alexei Levenkov Jan 27 '20 at 21:03
  • See also https://github.com/dotnet/csharplang/issues/1948 – Robert Harvey Jan 27 '20 at 21:28
  • 2
    @Alexei: the C# 5 spec (selected because I happened to have it handy) has basically the same exact text as referenced above. I find the behavior as puzzling as the OP does, as it's my understanding that without ".beforefieldinit", the runtime is supposed to execute constructors (including static, and including in structs) exactly according to spec. Only with that flag (not present here) is the runtime allowed to "prematurely" call a static constructor. And yet, here it does seem to. I don't see any rationale for this, even in the non-draft spec. – Peter Duniho Jan 27 '20 at 23:21
  • For what it's worth: inserting an extra writeline between the variable declaration and the method call, even if changing the variable to `Notebook[] notebook = new Notebook[1];` and the call to `notebook[0].Method()` (i.e. which forces the issue of what exactly is triggering init), shows that the init is triggered by the method call itself, not anything that occurs earlier. This is true even if the struct has a field (the lack of which is the only thing allowing this seemingly uninitialized local to be used...it's not uninitialized by C# rules, if it has no fields). – Peter Duniho Jan 27 '20 at 23:25
  • 1
    Also for what it's worth: this behavior has been noted on Stack Overflow at least as long ago as 2010: [Why doesn't the CLR always call value type constructors](https://stackoverflow.com/q/3246078). That question actually asks about the opposite -- why _isn't_ the static constructor called. But the accepted answer does discuss the fact that if any method is called, the static constructor will get called first (i.e. along the lines of @Alexei's earlier suggestion). – Peter Duniho Jan 28 '20 at 00:30

1 Answers1

2

It seems to me that this discrepancy is fundamentally a conflict between the C# language specification (most recent non-draft specification, i.e. C# 5.0, linked, but other versions are essentially identical in this respect) and the Common Language Infrastructure specification.

Specifically, the language specification gives the triggers you've mentioned, but the CLI specification includes this description for types not marked with the BeforeFieldInit flag (as is the case here):

The semantics of when and what triggers execution of such type initialization methods, is as follows:

4. If not marked BeforeFieldInit then that type’s initializer method is executed at (i.e., is triggered by):
    a. first access to any static field of that type, or
    b. first invocation of any static method of that type, or
    c. first invocation of any instance or virtual method of that type if it is a value type or
    d. first invocation of any constructor for that type.

(my emphasis)

In other words, for the runtime, the rules are explicitly that invoking any instance method of a type will cause the static constructor (i.e. the "type initializer") to be invoked. That's exactly how your program is behaving.

The language in the C# specification has persisted so long, through earlier versions of the specification all the way through the latest released version of the language (technically a "draft" specification, but still prescriptive), it's difficult for me to think that this discrepancy is a simple oversight. It could be, of course (barring input from the language designers). But I think it's just as likely that the language designers decided to be minimally restrictive in spite of the CLI's rules (since in theory, the C# language could target other similar platforms, platforms that might not be as restrictive as the CLI), and just live with the difference.

After all, the CLI implementation of a C# program, following its rules instead of the C# rules, is unlikely to be problematic for anyone. It should be exceedingly rare, and arguably very poor design, for a program to actually rely on the static constructor not being called when a method that accesses no static members is itself called.

Peter Duniho
  • 68,759
  • 7
  • 102
  • 136