Hidden Bug Caused by Static Member Initialization
I’ve encountered this bug a few times, and I wanted to share it with others.
The bug itself is simple once broken down, but it can be quite deceptive in real code. Let’s dive into the code and I’ll explain the issue step by step.
using System;
public sealed class Program
{
private Program(){}
public static Program Instance { get; } = new Program();
private static int _value = 100;
public int ExposedValue { get; } = _value;
public static void Main()
{
Console.WriteLine(Program.Instance.ExposedValue);
}
}
In the code above, we’re creating a singleton of the Program
class. It encapsulates a static field, _value
, which is initialized to 100
. The class exposes an instance property, ExposedValue
, which expects to return the value 100
.
However, when running the Main
method, the output is 0
instead of 100
. Let's break down why this happens.
-
Static Initializers Run in the Order They Appear in Code
Consider the following example:
static int value2 = value1; // What's the value of `value2`? => 0. static int value1 = 200;
Since static members are initialized in the order they appear, value2 is assigned 0 because value1 hasn’t been initialized yet.
-
Static Members Are Usually Initialized When Accessed
For instance, in the following class:
public class TestClass { public int ExposedValue { get; } = _value; public static int _value = 100; }
When you access
(new TestClass()).ExposedValue
, it will return100
as expected. This happens because the static member_value
is initialized before being used in the instance propertyExposedValue
.
Now, let’s focus on the line causing the issue:
public static Program Instance { get; } = new Program();
Here, the Instance
property is static, and it calls the constructor of Program.
The constructor initializes the instance members, including ExposedValue
.
However, due to the way static initializers work (they run in order of appearance in the source code), the _value
field is still at its default value (0
) when it’s used to initialize ExposedValue
. This happens because _value
is initialized later, after the Instance
property’s static initializer is called.
Thus, ExposedValue
gets assigned the value 0
instead of 100
.
Let me also call out, on the side, that is also an unusual situation when a constructor is called before a static constructor.
To fix the bug, we need to change the order of initialization so that the static fields are initialized in the correct order. Here’s the corrected version of the code:
using System;
public sealed class Program
{
private Program(){}
// The order of these two lines is flipped:
private static int _value = 100;
public static Program Instance { get; } = new Program();
public int ExposedValue { get; } = _value;
public static void Main()
{
Console.WriteLine(Program.Instance.ExposedValue);
}
}
Now, the ExposedValue
will correctly return 100
as expected.