🔍 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:

  • .cctor = class constructor = type initializer
  • It runs once per type
  • Triggered before any static field is accessed in the case of implicit .cctor. - With an explicit static constructor, it is triggered before any static member access or instance creation. - With implicit .cctor, methods and properties may not trigger it — only field access guarantees execution.
  • Automatically includes: 1 - Static field initializations 2 - Static constructor logic (if present)

🆚 Difference from .ctor:

  • .ctor: Instance constructor – runs every time you create an object
  • .cctor: Type initializer – runs only once, regardless of how many objects are created


🔁 Enter beforefieldinit

Not all .cctor executions are equal. The beforefieldinit flag affects when the runtime is allowed to trigger the type initializer.

Article content
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?


Article content
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

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

  • .cctor is generated for static fields.
  • beforefieldinit allows early (but unpredictable) type initialization.
  • Defining a static constructor disables beforefieldinit, ensuring predictable timing.
  • Static properties only initialize when accessed.
  • Instance fields initialize with object creation.
  • The execution of .cctor triggers the initialization of static fields and runs once per type.
  • The execution of .ctor triggers the initialization of static fields (if not already initialized), followed by instance fields.


🔐 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!

To view or add a comment, sign in

More articles by Mohamad H.

Insights from the community

Others also viewed

Explore topics