static constructor (靜態建構函式) 是 C# 類別初始化的關鍵機制,與 static readonly 欄位搭配可實現**執行緒安全、一次性、延遲**的靜態資料初始化。本文深度解析執行時機、循環相依順序、IL 差異與常見陷阱。
static ClassName() { ... } (無存取修飾詞、無參數、不可繼承、不可直接呼叫)static readonly 欄位只能在宣告處或靜態建構函式指派
class A
{
public static int X;
static A()
{
Console.WriteLine("static A()");
X = B.Y + 1; // 觸發 B 初始化
}
}
class B
{
public static int Y = A.X + 1; // 欄位初始化器 (靜態建構函式前執行)
static B()
{
Console.WriteLine("static B()");
}
}
class Program
{
static void Main()
{
Console.WriteLine($"X={A.X}, Y={B.Y}"); // 輸出順序見下
}
}
static B() // 1. Main 觸發 A 初始化 -> A 靜態建構函式執行前先跑欄位初始化器?
// 實際順序:
// 1. Main 存取 A.X -> 觸發 A 初始化
// 2. A 靜態建構函式開始執行
// 3. A 靜態建構函式執行 X = B.Y + 1 -> 觸發 B 初始化
// 4. B 欄位初始化器 Y = A.X + 1 執行 (此時 A.X 尚為 0) -> Y = 1
// 5. B 靜態建構函式執行 (無碼)
// 6. 返回 A 靜態建構函式 -> X = B.Y + 1 = 2
// 7. 返回 Main -> 輸出 X=2, Y=1
關鍵點:欄位初始化器在靜態建構函式**之前**執行,且**按文字順序**。循環相依會導致「看到對方尚未完全初始化的狀態」(上例中 B.Y 讀取 A.X 時為 0)。
| 類別標記 | 觸發時機 | 效能 |
|---|---|---|
| 有靜態建構函式 | 精確語義:首次存取 static 成員前 | 稍慢 (需同步檢查) |
| 無靜態建構函式 (僅欄位初始化器) | BeforeFieldInit:任意時機 (可能極早,如載入組件時) | 最快 (JIT 可自由優化) |
若類別**只有靜態欄位初始化器**,不寫 static Class() {},CLR 標記 BeforeFieldInit,允許在任何時間 (甚至第一次存取前很久) 初始化,效能最佳但語義較鬆。需精確控制初始化時機 (如單例、資源獲取) 時,請顯式宣告空的靜態建構函式。
TypeInitializationException),不可恢復。避免在靜態建構函式做可能失敗的 IO/網路操作,或包裝 try/catch 並設定降級值。
public sealed class Singleton
{
private static readonly Singleton _instance = new Singleton();
public static Singleton Instance => _instance;
// 顯式靜態建構函式 -> 精確語義、防止 BeforeFieldInit 過早初始化
static Singleton() { }
private Singleton() { /* 私有建構函式 */ }
}
static readonly 實現不可變靜態資料。BeforeFieldInit 與顯式靜態建構函式的效能/語義取捨。