測試代碼結構
class BaseA
{
public static string StaticField = "BaseA靜態";
public string InstanceField = "BaseA實例";
static BaseA() { Console.WriteLine("基類靜態構造"); }
public BaseA() { Console.WriteLine("基類構造函數"); }
}
class DerivedB : BaseA
{
public static string StaticField = "DerivedB靜態";
public string InstanceField = "DerivedB實例";
static DerivedB() { Console.WriteLine("派生類靜態構造"); }
public DerivedB() { Console.WriteLine("派生類構造函數"); }
}
static void Main()
{
var obj = new DerivedB();
}
實際輸出順序
派生類靜態構造
派生類實例字段初始化
基類靜態構造
基類實例字段初始化
基類構造函數
派生類構造函數
順序規律總結
| 階段 | 順序 | 說明 |
| 靜態初始化 | 派生類 → 基類 | 首次訪問派生類觸發,基類靜態構造在派生類靜態構造之後 |
| 實例字段初始化 | 派生類 → 基類 | 字段初始化器按繼承鏈從派生到基類執行 |
| 構造函數執行 | 基類 → 派生類 | 構造函數從基類到派生類執行 |
關鍵風險:派生類字段初始化器引用基類成員
class Base { public int Value = 10; }
class Derived : Base
{
public int DoubleValue = Value * 2; // 風險:此時基類字段已初始化,但基類構造函數未執行
}
雖然基類字段初始化器已執行(值為 10),但基類構造函數邏輯尚未運行,若構造函數修改 Value 會導致不一致。
對比 Java
- Java:靜態初始化基類→派生類;實例字段初始化基類→派生類;構造函數基類→派生類。完全自上而下。
- C#:靜態初始化觸發類優先;實例字段初始化派生類優先。設計考量:支持派生類字段初始化器使用基類靜態成員,但引入了上述風險。
記憶體分配與默認值
new DerivedB() 先在托管堆分配內存,所有實例字段零初始化(0/null/false)。
- 然後依次執行派生類字段初始化器 → 基類字段初始化器 → 基類構造函數 → 派生類構造函數。
C# 無構造函數初始化列表
不支持 Base(int v) : value(v) {} 語法,因為分配與初始化嚴格分離,且需支持反射創建實例(Activator.CreateInstance)。
下一篇預告
探討兩類相互引用靜態初始化的死鎖風險與解決方案(Lazy<T>、靜態構造函數延遲)。
- 上一篇文章: C# 類成員初始化順序(三):循環靜態依賴與結構體初始化
- 下一篇文章: C# 自定義動態圖像按鈕(二):動畫幀控制與定時器邏輯
Copyright ©
程式碼之家 All Rights Reserved