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
- Load configuration (connection strings, API keys, settings).
- Register types trong DI container, serializer.
- Initialize native resources (P/Invoke library load).
- Setup logging (configure sinks, levels).
- 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)