1

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
}
Christian Genne
  • 190
  • 3
  • 9
  • To access `NoStaticConstructor.Field` the class must call its static constructor, so all classes actually have one. – Poul Bak Oct 20 '22 at 05:36
  • If you're going to write a static code analysis tool to look for improperly initialized types, why not instead look for code that's dependent on the order of initialization of static fields. That seems enormously fragile and confusing to maintain, if the addition/removal of a blank static constructor changes the behavior of the code. – Servy Oct 20 '22 at 18:00

1 Answers1

3

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.