🔍 Demystifying Static Initialization in C#
Understanding when and how static members are initialized in C# can save you from subtle bugs and unpredictable behavior. Let's unravel .cctor (type initializer), beforefieldinit, and what really happens behind the scenes in a simple and structured way.
🧠 What’s the Big Deal?
You’ve probably written this before:
public static int X = 10;
But when is X initialized? Is it when the program starts? When the class is used? The answer isn’t always obvious — and depends on a few key factors.
🧱 Meet the Type Initializer (.cctor)
In .NET, all static field initializations and static constructors are compiled into a special method called the type initializer, represented in IL as .cctor.
Quick Facts:
🆚 Difference from .ctor:
🔁 Enter beforefieldinit
Not all .cctor executions are equal. The beforefieldinit flag affects when the runtime is allowed to trigger the type initializer.
Note: This flag is not an actual boolean; it’s a metadata marker the compiler adds (or omits) based on how your class is defined.
⚙️ When Does the Compiler Generate .cctor?
Even static readonly int X = 10; causes a .cctor, unlike const int X = 10; which does not.
💡 Example: With Static Constructor
class WithCtor
{
public static int X = Init("X");
public static int Y = Init("Y");
static WithCtor() { }
static int Init(string name)
{
Console.WriteLine($"{name} initialized");
return 0;
}
}
Console.WriteLine(WithCtor.X);
Output:
X initialized
Y initialized
0
➡️ All static fields were initialized because .cctor ran before first use of the type. The explicit static constructor disables beforefieldinit, guaranteeing deferred initialization.
💡 Example: Without Static Constructor
Recommended by LinkedIn
class NoCtor
{
public static int X = Init("X");
public static int Y = Init("Y");
public static int Z => 10;
static int Init(string name)
{
Console.WriteLine($"{name} initialized");
return 0;
}
}
Console.WriteLine(NoCtor.Z);
Output:
10
➡️ The static fields X and Y remained uninitialized because only the static property Z was accessed. Since there is no explicit static constructor, the type carries the beforefieldinit flag, which permits the runtime to postpone the execution of the type initializer (implicit .cctor) until a static field is directly accessed. As a result, accessing Z did not trigger the initialization of X and Y.
💡 Example: Consistent Behavior With and Without Static Constructor
class WithCtor
{
public static int X = Init("X");
public static int Y = Init("Y");
static WithCtor() { }
static int Init(string name)
{
Console.WriteLine($"{name} initialized");
return 0;
}
}
Console.WriteLine(WithCtor.X);
class WithCtor
{
public static int X = Init("X");
public static int Y = Init("Y");
WithCtor() { }
static int Init(string name)
{
Console.WriteLine($"{name} initialized");
return 0;
}
}
Console.WriteLine(WithCtor.X);
Output:
X initialized
Y initialized
0
👉 When you access the static field X, the type initializer runs and initializes all static fields, not just X. This happens even if you haven’t defined an explicit static constructor, because accessing any static field (unlike a property or method) automatically triggers the implicit type initializer. However, if you explicitly define a static constructor, it disables the beforefieldinit flag. This ensures that initialization is delayed until the type is first used, at which point all static fields are initialized together.
🔄 Static Properties Only Run on Access
public static int Property => Init("Prop");
This initializer only runs when the property is accessed — not before.
🧍 What About Instance Fields?
public int Z = Init("Z");
Instance fields initialize only when you create an object:
var obj = new MyClass(); // Triggers Z initialization
✅ Summary
🔐 Pro Tip
Want to control when static fields are initialized? Add an empty static constructor:
static MyClass() { }
This ensures your type is initialized only when it's used — not before.
Thanks for reading! If you found this helpful, feel free to connect, share, or comment.
🟢 Happy coding!