Static Initialization

The way static fields are initialized has been changed across .net framework version. As the framework matured, static fields have become initialized more lazily.

Today I will show a quick comparison between .net472 and .netcoreapp3.0 focused on static field initialization. I will use the following application to demonstrate the issue.

class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine("Start");
        var test = new Test();
        test.DoWork();
        Console.WriteLine("Start work");
        test.DoWork2();
    }
}

public class Test
{
    private static readonly Logger logger = new Logger();
    public Test() => Console.WriteLine("TestCtr");
    public void DoWork() => Console.WriteLine("Working...");
    public void DoWork2()
    {
        Console.WriteLine("Working2...");
        logger.Log("Completing work");
    }
}

public class Logger
{
    public Logger() => Console.WriteLine("LoggerCtr");
    public void Log(string message) => Console.WriteLine(message);
}

As normally, someone using C# for a while you would expect static fields and class constructor (or static constructor) run and initialized before the first instance of a class being created and used. Now this is not entierly true, as we will see it in the following sections.

.net472 with Debug mode

Running the above application in .net472 with Debug mode, the previous statement still looks correct. The output of the application is:

Start
LoggerCtr
TestCtr
Working...
Start work
Working2...
Completing work

Before the Test class is being instanciated the Logger's class ctr runs, because Test class has a static field typed Logger.

.net472 with Release mode

Changing to Release mode might result the first unexpected behavior. You can instanciate and even invoke a method of a class, and the static field is still not initialized. See how Test class's ctr., DoWork methods both executed before the static field got intialized.

Start
TestCtr
Working...
Start work
LoggerCtr
Working2...
Completing work

The static field variable initializers of a class correspond to a sequence of assignments that are executed in the textual order in which they appear in the class declaration. If a static constructor (Static constructors) exists in the class, execution of the static field initializers occurs immediately prior to executing that static constructor. Otherwise, the static field initializers are executed at an implementation-dependent time prior to the first use of a static field of that class.Ref: Source

As there is no static constructor in the Test class, it is absolutely fine for the runtime to do the implementation-dependent time prior to the first use . This has been causing several production bugs and uncomprehending developers, not understanding why everything still looks correct when they debug their application.

netcore and Debug or Release mode

Fortunately, .net core fixes this issue of having different behavior in Debug and Release mode. I am testing with .net core 2.1. Running the same application now results a nearly similar output as the full framework in Release mode, though it is still different, resulting a 3rd behavior:

Start
TestCtr
Working...
Start work
Working2...
LoggerCtr
Completing work

In this configuration not only the Test class's ctr., DoWork but even DoWork2 methods start execution before the static field gets initialized.Although this seems a subtle difference, it can cause many production bugs and headaches, so it is better keep an eye on static fields. To overcome the behavior we can always add an empty static constructor, so that the static fields get initialized upfront too.

On the plus side, .net core works the same way in Debug and Release (at least on Windows platform), as well as code can be even step-by-step debugged through.