Static constructors, part two

Previously on FAIC I gave a quick overview of the basic operation of a static constructor. Today, three unusual corner cases.

The first odd case I want to talk about involves static methods. Take a look at the sample program from last time. Now suppose we edited the Main method to say:

static void Main() 

First off, is that even legal? Sure! Inheritance means that all inheritable members of B are also members of D. M is an inheritable member of B, so it is a member of D, right?

Unfortunately, this corner case is the one that exposes the leaky abstraction. The compiler generates code as though you had said B.M();, and therefore D’s static constructor is not called even though “a member of D” has been invoked. This actually makes a fair amount of sense. The method B.M is going to be called, and there’s no reason to go to all the work of running D’s static constructor when B.M probably does not depend on any work done by D’s constructor. And it would seem strange if calling the same method by two different syntaxes would result in different static constructor invocations.

Now let’s consider a second case involving static method invocation. Suppose now we edited Main to say:

static void Main() 

Clearly D’s static constructor must be invoked. What about B? Is its static constructor invoked? No! A static constructor is triggered by a usage of a static member, or by the creation of an instance. Invoking D.N does not use any static member of B and it does not create an instance of B, so B’s static constructor is not invoked. People sometimes expect that static constructors of base classes will always be invoked before static constructors of derived classes, but that’s not the case.

Our third odd case is: what happens when a static constructor throws an exception?

Absolutely nothing good! First off, of course if the exception goes unhandled then all bets are off. The runtime is permitted to do anything it likes if there is an unhandled exception, including such options as starting up a debugger, terminating the appdomain immediately, terminating the application after running finally blocks, and so on. And an exception in a static constructor can easily go unhandled; trying to wrap every possible first usage of a type with a try-catch block is onerous.

And even if by some miracle the exception gets handled the first time, odds are very good that your program is now in such a damaged state that it is going to go down in flames soon. Remember, I said that a static constructor runs once, and by that I meant once; if it throws, you don’t get a second chance. Instead, when a static constructor terminates abnormally, the runtime marks the type as unusable, and every attempt by your program to use that type results in another exception.

An interesting fact about static constructors that throw exceptions is that when the runtime detects that a static constructor has terminated abnormally, it wraps the exception in its own exception and throws that instead. Check out this StackOverflow answer, where Jon demonstrates this in action.

Next time on FAIC: I’ll defer to Jon again when I discuss how the runtime is permitted to optimize some static constructors.

11 thoughts on “Static constructors, part two

  1. I could see the “only try once” thing causing some very interesting behavior where there is an app domain that lasts for a while, like say in an ASP.Net web app.

  2. Another corner case you may (or not) want to touch on. Is given
    class C {
    public static int i = 3;
    One can read C.i as 0 🙂
    aka CLR avoids cycles and deadlocks

      • Try accessing any member of C in the following code:

        class C
        public static int k = D.GetK();
        public static int i = 3;

        class D
        public static int GetK()
        if (C.i == 0)
        throw new Exception(“Oh noes!”);

        return 42;

        • You’re example is not valid. The order in which you are initializing the value of i is completely different from what you hinted in your first post.

  3. “And it would seem strange if calling the same method by two different syntaxes would result in different static constructor invocations.”

    It also seems strange that calling two static methods through the same type could result in different static constructors executing. A user could reasonably expect that both

    static void Main() { D.M(); }


    static void Main() { D.N(); }

    would each invoke the same static constructors (whether B’s is included or not is a separate matter). In this case, part of me thinks that by calling M() through D, the user has made a choice, perhaps unconsciously, that D needs to be initialized before the call to M(). Another part of me thinks that C# was designed very carefully to avoid such subtleties. Was there more to this decision than just being able to avoid the runtime cost of invoking D’s static initializers unnecessarily?

  4. Intersting… At least there is a well defined behaviour for letting the static constructor tell the type cannot be created.

    I wonder if who wrote this part of the spec was thinking on the usage I am thinking about.

  5. A few more corner cases that may be worthy of note:

    -1- Static constructors for value types do not seem to run run until an instance property or method [but not field] is accessed, a parameterized constructor is used, `Activator.CreateInstance()` is called, or a static member [including field] of the type is accessed, even though value-type instances can come into existence and (if fields are exposed) be used without any of those things happening.

    -2- The instantiation of T[] does not imply the creation of type T, whether T is a class or a struct.

    Somewhat related to point 2, if one has a generic method with a type parameter `T` that may or may not have a public constructor, is there a nice way for that method to force type `T` to be constructed if it hasn’t been yet?

  6. Pingback: Линкблог #15

  7. Pingback: Static constructors, part one | Fabulous adventures in coding

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s