NeuroAgent

C# TypeInitializationException: Static Constructor Issues

Learn why C# throws TypeInitializationException when base class constructors access uninitialized static lists. Discover the static initialization order and solutions to prevent this common error.

Question

Why does C# throw a TypeInitializationException when a base class constructor tries to add to a static list that should have been initialized by the static constructor, but is null?

NeuroAgent

When a C# base class constructor tries to add to a static list that should have been initialized by the static constructor but is null, it throws a TypeInitializationException because the static constructor hasn’t executed yet, leaving the static field in its default null state, and when the constructor attempts to modify this null list, it triggers an exception that gets wrapped by the CLR in a TypeInitializationException.

Contents

Understanding TypeInitializationException

A TypeInitializationException is a CLR exception that occurs when a type fails to initialize. According to Microsoft documentation, “If an exception is thrown in a static constructor, that exception is wrapped in a TypeInitializationException exception, and the type cannot be instantiated.”

This exception serves as a wrapper for exceptions that occur during static initialization, making debugging more challenging because the original exception might be lost or obscured. The CLR uses this mechanism to ensure that once a static constructor fails, the type remains in an unusable state for the entire application domain.

Static Field Initialization Order

Static field initialization follows a specific sequence that’s crucial to understand:

  1. Default Value Assignment: When the process loads, all static fields are first set to their default values (0 for integers, false for booleans, null for reference types)

  2. Static Initializers: Static field initializers (those in the form private static readonly List<string> _items = new List<string>();) run in textual order as they appear in the source code

  3. Static Constructor: The static constructor (static constructor) runs after all static initializers

Important: Static initializers are executed before any static constructors are called, and the order of static constructor execution isn’t specified.

The Problematic Scenario

The specific issue described occurs when:

csharp
public class BaseClass
{
    public static List<string> SharedItems { get; private set; }
    
    static BaseClass()
    {
        // This should initialize SharedItems
        SharedItems = new List<string>();
    }
    
    public BaseClass()
    {
        // This constructor runs BEFORE the static constructor!
        SharedItems.Add("item"); // Throws NullReferenceException
    }
}

The problem is that when you create an instance of BaseClass, the instance constructor executes before the static constructor. At this point, SharedItems is still null (its default value), so calling SharedItems.Add() throws a NullReferenceException that gets wrapped by the CLR in a TypeInitializationException.

Why the Static Constructor Doesn’t Run

The static constructor only runs when:

  • A static member is accessed for the first time, OR
  • An instance of the class is created for the first time

However, there’s a timing issue. When you create an instance:

  1. The memory for the instance is allocated
  2. Instance fields are initialized to default values
  3. Instance field initializers run in textual order
  4. The base class constructor runs (if applicable)
  5. The instance constructor runs

The static constructor runs after step 4, which means the base class constructor (and any base class constructors) execute before the static constructor of the current class.

This creates a race condition where the constructor assumes the static fields are already initialized, but they haven’t been.


Solutions and Best Practices

1. Use Lazy Initialization

csharp
public class BaseClass
{
    private static readonly Lazy<List<string>> _sharedItems = 
        new Lazy<List<string>>(() => new List<string>());
    
    public static List<string> SharedItems => _sharedItems.Value;
    
    public BaseClass()
    {
        // Now safe - will create the list if not already created
        SharedItems.Add("item");
    }
}

2. Initialize Static Fields Directly

csharp
public class BaseClass
{
    // Initialize directly rather than in static constructor
    public static List<string> SharedItems { get; } = new List<string>();
    
    public BaseClass()
    {
        // Safe to use now
        SharedItems.Add("item");
    }
}

3. Check for Null Before Using

csharp
public class BaseClass
{
    public static List<string> SharedItems { get; private set; }
    
    static BaseClass()
    {
        SharedItems = new List<string>();
    }
    
    public BaseClass()
    {
        // Defensive programming
        SharedItems?.Add("item");
    }
}

4. Avoid Static Dependencies in Constructors

The cleanest solution is to avoid having constructors depend on static fields. Instead, use instance fields or dependency injection.

Real-World Examples

Example 1: Configuration Classes

csharp
public class ConfigManager
{
    public static Dictionary<string, string> Settings { get; private set; }
    
    static ConfigManager()
    {
        Settings = LoadSettings();
    }
    
    public ConfigManager()
    {
        // Problem: Settings might not be loaded yet
        var value = Settings["key"]; // Could throw TypeInitializationException
    }
}

Example 2: Registry Pattern

csharp
public class ServiceRegistry
{
    public static List<IService> Services { get; private set; }
    
    static ServiceRegistry()
    {
        Services = new List<IService>();
    }
    
    public ServiceRegistry(IService service)
    {
        // Problem: Services list is null when constructor runs
        Services.Add(service); // Throws NullReferenceException
    }
}

The key insight is that static constructors run after the first instance constructor call, not before. This means you should never assume static fields are initialized when an instance constructor executes. Always use lazy initialization, direct field initialization, or defensive null checks to avoid TypeInitializationException issues.