Computer >> Hướng Dẫn Máy Tính >  >> Lập Trình >> C#

Static Constructor trong C#: Khi nào được gọi và cách sử dụng

Static Constructor là gì?

Constructor đặc biệt dùng để khởi tạo static fields hoặc thực hiện hành động chỉ một lần trước khi class được sử dụng.

Đặc điểm quan trọng

  • Tên: Trùng tên class, không có access modifier.
  • Không tham số: Không thể truyền đối số.
  • Chỉ một: Mỗi class/struct chỉ có tối đa 1 static constructor.
  • Tự động gọi bởi CLR: Không thể gọi thủ công.
  • Thời điểm: Trước khi tạo instance đầu tiên HOẶC truy cập static member đầu tiên.
  • Không kế thừa/overload: Không thể override, không có base static constructor.

Cú pháp


public class Program 
{
    // Static constructor
    static Program() 
    {
        Console.WriteLine("Static constructor executed");
        // Khởi tạo static fields, load config, register types...
    }
    
    // Instance constructor
    public Program() 
    {
        Console.WriteLine("Instance constructor executed");
    }
    
    static void Main() 
    {
        Console.WriteLine("Main started");
        var p1 = new Program();
        var p2 = new Program();
    }
}

Kết quả

Main started
Static constructor executed
Instance constructor executed
Instance constructor executed

Ví dụ thực tế: Configuration Loading


public class AppSettings 
{
    public static string ConnectionString { get; private set; }
    public static string ApiKey { get; private set; }
    public static int MaxRetries { get; private set; }
    
    static AppSettings() 
    {
        Console.WriteLine("Loading configuration...");
        
        // Giả lập load từ file/config
        ConnectionString = "Server=localhost;Database=MyApp;";
        ApiKey = "sk-1234567890abcdef";
        MaxRetries = 3;
        
        // Validation
        if (string.IsNullOrEmpty(ConnectionString))
            throw new InvalidOperationException("ConnectionString not configured");
    }
}

// Sử dụng
class Program 
{
    static void Main() 
    {
        // Lần đầu truy cập static property → trigger static constructor
        Console.WriteLine(AppSettings.ConnectionString);
        
        // Lần 2 - static constructor KHÔNG chạy lại
        Console.WriteLine(AppSettings.ApiKey);
    }
}

Static Constructor vs Static Field Initializer


public class Example 
{
    // Field initializer - chạy trước static constructor
    static readonly string FieldInit = InitField();
    
    // Static constructor - chạy sau field initializers
    static Example() 
    {
        Console.WriteLine("Static ctor");
    }
    
    static string InitField() 
    {
        Console.WriteLine("Field initializer");
        return "value";
    }
}

// Output:
// Field initializer
// Static ctor

Exception trong Static Constructor

⚠️ Nguy hiểm: Nếu static constructor throw exception → TypeInitializationException được throw, và class trở nên unusable cho toàn bộ AppDomain. Mọi truy cập sau đều throw TypeInitializationException.

public class Dangerous 
{
    static Dangerous() 
    {
        throw new InvalidOperationException("Config missing!");
    }
    
    public static void DoSomething() { }
}

// Usage
try 
{
    Dangerous.DoSomething();
}
catch (TypeInitializationException ex) 
{
    Console.WriteLine($"Type broken: {ex.InnerException.Message}");
}

// Sau này gọi lại vẫn fail
Dangerous.DoSomething(); // TypeInitializationException again!

Thread Safety

  • CLR đảm bảo static constructor chạy duy nhất một lần (thread-safe).
  • Sử dụng lock nội bộ (type initializer lock).
  • Không cần lo deadlock trong static constructor (tránh lock phức tạp).

BeforeFieldInit Attribute


// Nếu CHỈ có field initializers, KHÔNG có static constructor explicit
// → CLR có thể đánh dấu BeforeFieldInit (chạy khi nào CLR muốn, trước first access)

public class LazyInit 
{
    static readonly string Value = Compute(); // BeforeFieldInit = true
}

// Nếu CÓ static constructor explicit
// → BeforeFieldInit = false (chạy chính xác trước first access)

public class EagerInit 
{
    static readonly string Value;
    static EagerInit() { Value = Compute(); } // BeforeFieldInit = false
}

Use Cases phổ biến

  1. Load configuration (connection strings, API keys, settings).
  2. Register types trong DI container, serializer.
  3. Initialize native resources (P/Invoke library load).
  4. Setup logging (configure sinks, levels).
  5. Pre-compute lookup tables (cache expensive calculations).

Static Constructor trong Generic Class


public class Repository 
{
    static Repository() 
    {
        // Chạy 1 lần cho MỖI closed generic type
        // Repository và Repository có static constructor riêng biệt
        Console.WriteLine($"Static ctor for {typeof(T).Name}");
    }
}

// Repository -> runs once
// Repository -> runs once (riêng biệt)