Static Initialization
10/22/2019
3 minutes
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.