掌握 C# 物件初始化順序對避免微妙 Bug 至關重要。本文透過完整範例程式與輸出追蹤,詳細闡述八個階段的執行序列,並揭露「基類建構函式呼叫虛擬方法」的經典陷阱與兩階段建構解法。
class Base {
static Base() { Console.WriteLine("Base.Static.Constructor"); }
public Base() {
Console.WriteLine("Base.Instance.Constructor");
m_Field3 = new Tracker("Base.Instance.Field3");
Virtual(); // 危險:呼叫虛擬方法
}
private Tracker m_Field1 = new Tracker("Base.Instance.Field1");
private Tracker m_Field2 = new Tracker("Base.Instance.Field2");
private Tracker m_Field3;
static private Tracker s_Field1 = new Tracker("Base.Static.Field1");
static private Tracker s_Field2 = new Tracker("Base.Static.Field2");
public virtual void Virtual() { Console.WriteLine("Base.Instance.Virtual"); }
}
class Derived : Base {
static Derived() { Console.WriteLine("Derived.Static.Constructor"); }
public Derived() {
Console.WriteLine("Derived.Instance.Constructor");
m_Field3 = new Tracker("Derived.Instance.Field3");
}
private Tracker m_Field1 = new Tracker("Derived.Instance.Field1");
private Tracker m_Field2 = new Tracker("Derived.Instance.Field2");
private Tracker m_Field3;
static private Tracker s_Field1 = new Tracker("Derived.Static.Field1");
static private Tracker s_Field2 = new Tracker("Derived.Static.Field2");
public override void Virtual() { Console.WriteLine("Derived.Instance.Virtual"); }
}
class Tracker { public Tracker(string text) { Console.WriteLine(text); } }
Derived.Static.Field1
Derived.Static.Field2
Derived.Static.Constructor
Derived.Instance.Field1
Derived.Instance.Field2
Base.Static.Field1
Base.Static.Field2
Base.Static.Constructor
Base.Instance.Field1
Base.Instance.Field2
Base.Instance.Constructor
Base.Instance.Field3
Derived.Instance.Virtual ← 派生類虛擬方法在基類建構函式中被呼叫!
Derived.Instance.Constructor
Derived.Instance.Field3
輸出顯示 Derived.Instance.Virtual 在 Base.Instance.Constructor 期間執行,此時派生類建構函式尚未執行,Derived.m_Field3 仍為 null。若虛擬方法存取該欄位將導致 NullReferenceException。
Derived d = new Derived(); // 階段一:完成物件建構
d.Virtual(); // 階段二:安全呼叫虛擬方法
深入理解初始化順序,是寫出健壯 C# 程式碼的基本功。