物件導向語言中,基底類別與衍生類別的初始化順序直接影響物件狀態一致性。本文對比 C#、Java、VB.NET 三種語言的建構子執行順序,揭示關鍵差異。
public class BaseClass {
public string a = "BaseClass";
public BaseClass() {
a = "初始化BaseClass";
}
}
public class ExtendClass : BaseClass {
private string b = "ExtendClass";
public ExtendClass() : base() {
b = "初始化ExtendClass";
}
}
// 使用方式
BaseClass obj = new ExtendClass();
| 步驟 | C# | Java | VB.NET |
|---|---|---|---|
| 1 | 衍生類別欄位初始化(b = "ExtendClass") | 基底類別欄位初始化(a = "BaseClass") | 基底類別欄位初始化(a = "BaseClass") |
| 2 | 基底類別欄位初始化(a = "BaseClass") | 衍生類別欄位初始化(b = "ExtendClass") | 基底類別建構子(a = "初始化BaseClass") |
| 3 | 基底類別建構子(a = "初始化BaseClass") | 基底類別建構子(a = "初始化BaseClass") | 衍生類別欄位初始化(b = "ExtendClass") |
| 4 | 衍生類別建構子(b = "初始化ExtendClass") | 衍生類別建構子(b = "初始化ExtendClass") | 衍生類別建構子(b = "初始化ExtendClass") |
C# 保證衍生類別的欄位初始化器在基底類別建構子執行之前完成。這意味著基底建構子執行時,衍生類別的欄位已有初始值,但衍生類別建構子本體尚未執行。這設計允許基底建構子透過虛擬方法存取衍生類別欄位(雖有風險)。
Java 嚴格遵循「父類先完全初始化」原則:基底類別欄位初始化 → 基底建構子 → 衍生類別欄位初始化 → 衍生建構子。這確保基底建構子執行時,衍生類別欄位尚未初始化(仍為預設值),較安全但較不靈活。
VB.NET 將基底類別的欄位初始化與建構子視為不可分割的單元,先於衍生類別任何初始化完成。這介於 C# 與 Java 之間。
// C# 危險範例
public class Base {
public Base() { Initialize(); } // 呼叫虛擬方法
protected virtual void Initialize() { }
}
public class Derived : Base {
private List<string> items = new List<string>(); // 欄位初始化
protected override void Initialize() {
items.Add("test"); // NullReferenceException!items 尚未初始化
}
}
在 C# 中,若基底建構子呼叫被覆寫的虛擬方法,且該方法存取衍生類別欄位,將導致 NullReferenceException,因為衍生類別建構子本體尚未執行(雖欄位初始化器已跑過)。
base():雖 C# 隱含呼叫無參數基底建構子,但顯式宣告意圖更清晰C# 採用「衍生欄位先行」策略,Java 採用「基底完全優先」,VB.NET 採用「基底整體優先」。理解差異對編寫健壯繼承體系、避免微妙 Bug 至關重要。現代 C# 開發傾向使用工廠方法或初始化模式替代複雜建構子邏輯,從根本規避順序問題。