Search code examples
c#reflectionecma

Check if a class has an explicit static constructor?


In the ECMA standard under $14.5.6.2 it states that having a static constructor affects the static field initialization order. I want a way to enforce this, but cannot find a way to check for an explicit static constructor in a type. It seems like the C# runtime automatically generates one if there isn't one already (and if the class has some static fields). Is it possible to check if a class has an explicit static constructor using reflection?

For example - the following will return a constructor in both these cases, and I cannot see any difference between them:

static class NoStaticConstructor
{
    public static string Field = "x";
}

static class HasStaticConstructor
{
    static HasStaticConstructor() {}
}

public void Test()
{
    typeof(NoStaticConstructor)
        .GetConstructors(BindingFlags.Static | BindingFlags.NonPublic)
        .ShouldBeEmpty(); // FAILS

    typeof(HasStaticConstructor)
        .GetConstructors(BindingFlags.Static | BindingFlags.NonPublic)
        .ShouldNotBeEmpty(); // SUCCEEDS
}

Solution

  • If you look at the generated IL for both classes, you'll see that both of them have a static constructor:

    .method private hidebysig specialname rtspecialname static 
        void .cctor () cil managed 
    { /*...*/ } // end of method HasStaticConstructor::.cctor
    
    .method private hidebysig specialname rtspecialname static 
        void .cctor () cil managed 
    { /*...*/ } // end of method NoStaticConstructor::.cctor
    

    So, querying the constructor won't help you because it exists in the compiled code regardless of whether or not it's explicitly written in the C# code.

    The good news is, there's actually no need to check for the constructor. Instead, you should look for TypeAttributes.BeforeFieldInit. As mentioned here, the beforefieldinit flag is applied by default unless the class has a static constructor. That gives you the information that you're looking for with regards to the static field initialization order.

    We can write a helper method to check for that:

    static bool HasLazyInitialization(Type t) =>
        t.Attributes.HasFlag(TypeAttributes.BeforeFieldInit);
    

    Usage:

    Console.WriteLine(HasLazyInitialization(typeof(NoStaticConstructor)));  // true.
    Console.WriteLine(HasLazyInitialization(typeof(HasStaticConstructor)));  // false.