Static constructors, part three

Earlier in this series I recommended that you brush up on how instance constructors work; if you did, then you’ll recall that instance field initializers are essentially moved into the beginning of an instance constructor at a point before the call to the base class constructor. You might think that static field initializers work the same: a static field initializer is silently inserted at the beginning of the static constructor. And that’s true. Well, it is mostly true.

In the case where there is already a static constructor, even an empty one, that’s what happens: the field initializers become the prologue to the body of the static constructor, and the usual rules for static constructors then apply. (That is, the static constructor is invoked immediately before the first static member access or instance constructor access.) But suppose there is no user-supplied static constructor. Then what happens?

The C# compiler is not bound by the rules of static constructors in this case, and in fact, does not treat your program as though there was an empty static constructor that has static field initializers in it. Rather, it tells the runtime that the runtime may choose when to run static field initializers, entirely at its discretion, just so long as all the fields are initialized before they are used.

The runtime is still responsible for ensuring that fields are initialized in the right order, because one static field might be initialized based on the contents of another. This is a bad idea, but it is legal, so the compiler has to honour that.

In this scenario the runtime is permitted to run static field initializers as late as possible; it could wait until a static field is actually accessed, rather than waiting for any static member or instance constructor to be accessed. The runtime is also permitted to run static field initializers as early as possible; it could decide to run all the field initializers at once at the beginning of the program, even if the class in question was never used. It is up to the runtime implementation to decide.

Pre .NET 4 implementations of the runtime make an interesting choice; they run static field initializers of classes that have no static constructors when the first method that refers to the class is jitted. If we have:

class Alpha 
{
  static int x = 123;
  static void M() { }
}
class Bravo
{
  static int y = 456;
  static void N() { }
}
class Charlie
{
  static void Q(bool b)
  {
    if (b) Alpha.M(); else Bravo.N();
  }
  static void Main() 
  { 
    Q(true); 
  }
}

Then when Q is jitted, the field initializers for both x and y will be executed. If Bravo had a static constructor then it would not be initialized, because Bravo’s static constructor is only triggered by the actual execution of the call.

The runtime makes this rather odd-seeming choice as an optimization; this way it doesn’t have to generate code on every static member access that checks to see if the field initializers have run yet! It knows that if you get as far as accessing a static member, then the method that does so must have been jitted, and therefore the field initializers have been run already.

I originally believed this to be the case in .NET 4 as well, but Jon informs me that current versions of the runtime are even lazier than that. See Jon’s comment below for details. (Thanks Jon!)

I find this to be one of the strangest C#/CLR features and for many years I did not understand it at all well — and, since Jon has corrected my understanding of it, apparently I still don’t! Every time I encountered a question about it, I just referred the questioner to Jon’s excellent page on the subject. For more information and discussion on this rather odd feature, check it out.


Next time on FAIC: We’ll finish up this series by looking at an abuse of static constructors.

14 thoughts on “Static constructors, part three

  1. I believe the behaviour you described *was* true, before .NET 4.

    In .NET 4, the implementation details around type initialization changed – making things much lazier. We can observe that by changing your sample code to make the initializers for X and Y call methods instead of using constants – you can then introduce diagnostic output in the methods.

    The static field initializers don’t need to be run until the *field* is first used, even if a static method in the same class is called. So in your sample code, I believe that M will be called without either x or y being initialized, when running under .NET 4.

    Diagnostic-enabled version:

    using System;
    
    class Alpha 
    {
        static int x = GetX();
        public static void M()
        {
            Console.WriteLine("M");
        }
        
        static int GetX()
        {
            Console.WriteLine("GetX called");
            return 123;
        }
    }
    class Bravo
    {
        static int y = GetY();
        public static void N() 
        {
          Console.WriteLine("N");
        }
        static int GetY()
        {
            Console.WriteLine("GetX called");
            return 456;
        }
    }
    class Charlie
    {
        static void Q(bool b)
        {
            if (b) Alpha.M(); else Bravo.N();
        }
        static void Main() 
        { 
            Q(true); 
        }
    }

    This is all “on my machine” of course – it’s entirely possible that it depends on the mode of the JIT, CPU architecture etc.

    I blogged about this when I first noticed it:
    http://msmvps.com/blogs/jon_skeet/archive/2010/01/26/type-initialization-changes-in-net-4-0.aspx

  2. I ran into this recently—I’d written code that depended on the order of initialization. Curiously, the ‘laziness’ only happened on ‘Release’ builds, making the issue a bit harder to debug than I might have liked.

  3. Just to clarify something:
    From the point of view of CLR: static constructor is either there or not. It doesn’t really care about fields initializers.
    The rule:
    If not marked BeforeFieldInit then that type’s initializer method is executed at (i.e., is triggered by):
    first access to any static or instance field of that type, or
    first invocation of any static, instance or virtual method of that type

    Is a very strong condition:

    Consider a loop:
    while(cond) {
    access something stuff
    Touch member of C with ctor and no BeforeFieldInit
    }

    JIT must generate (and does) the check for static ctor being called inside of the loop. It can’t hoist it.

    • Could the JIT legitimately replace such a loop with:

      if(cond) {
      access something stuff
      Touch member of C with ctor and no BeforeFieldInit
      while(cond) {
      access something stuff
      Touch member of C with ctor and no BeforeFieldInit
      }
      }

      so as to only have the check executed on the first pass through the loop, rather than on every pass?

  4. How does the runtime know that a class doesn’t have a static constructor? Don’t field initializers appear in a compiler-generated static constructor?

    I can’t tell any difference between the two .cctors with ildasm.

    • The article kind of implies that static field initializers are something understood by the CLR but, as Dmitry says, the CLR only understands static constructors.

      Field initializers are always implemented with code at the start of a static constructor. If you have two identical classes with field initializers, except that one has an empty static constructor, the C# compiler outputs exactly the same static constructor for both of them.

      The difference is that the class without a static constructor is marked beforefieldinit.

  5. This has been a very educational series for me. Information you’ve posted has already made me refactor several classes to remove stuff from static constructors — the behavior around exceptions, in particular, made my code more difficult to debug if anything went wrong. I am looking forward to the last installment in the series to see if I recognize my old code in your examples of static constructor abuse. One request: Could you also give an example of an appropriate use of static constructors? Right now, I lean towards keeping them out of my own code for almost every case, but I wonder if that is an overreaction.

  6. Eric, do you know the rational behind leaving it implementation defined as to when static members are initialized in the absence of a static constructor? The optimization benefits seem minimal and the possibility for accidental bugs seems enormous. This definitely is not an example of C# providing a “pit of quality”, but rather a pit full of landmines.

    It seems to me the compiler should create an empty static constructor to prevent this kind of willy-nilly CLR-version-dependent behavior.

  7. Today we came across a situation where a static field was not initialized before first use of the class like we had expected. This article explains very well why that is the case. The interesting part was that when we used Visual Studio to run our program we didn’t experience the problem. It wasn’t until we tried running the exe as a stand-alone that we experienced the issue.

Leave a comment