在設計物件導向架構時,父類別建構子往往需要特定參數初始化,子類別需透過 base 關鍵字傳遞這些參數。然而,當子類別本身也需要使用該參數時,會遭遇「物件尚未建立完成,無法存取自身屬性」的經典困境。本文將深入剖析這個問題,並提供優雅的解決方案。
假設我們有一個 ParentClass 接受 IEnumerable<string> 作為建構參數:
public class ParentClass
{
public ParentClass(IEnumerable<string> dataCollection)
{
this.DataCollection = dataCollection;
}
public IEnumerable<string> DataCollection { get; private set; }
}
子類別 ChildClass 想要建立一個 List<string> 並同時傳給父類別與自身保存。直覺寫法如下:
public class ChildClass : ParentClass
{
private readonly List<string> _dataCollection = new List<string>();
// 編譯錯誤:建構子階段無法存取 _dataCollection
private ChildClass() : base(_dataCollection) { }
}
編譯器報錯:在 base() 呼叫前,物件尚未完全建構,無法存取實例欄位 _dataCollection。這是 C# 物件建構生命週期的鐵律:先執行基底建構子,再初始化衍生類別欄位,最後執行衍生類別建構子本體。
核心思路:引入私有建構子 接收已建立的集合,公開建構子負責建立集合並鏈式呼叫私有建構子。
public class ChildClass : ParentClass
{
private readonly List<string> _dataCollection;
// 公開無參數建構子:建立集合並委派給私有建構子
public ChildClass() : this(new List<string>()) { }
// 私有建構子:接收現成集合,傳給基底並賦值給自身欄位
private ChildClass(List<string> dataCollection) : base(dataCollection)
{
_dataCollection = dataCollection;
}
}
public ChildClass() → 建立 new List<string>() → this(list) → base(list) (父類別初始化) → _dataCollection = list (子類別欄位賦值)此模式天然支援依賴注入:
// 允許外部注入特定集合實現 (如 ConcurrentBag、ObservableCollection 等)
internal ChildClass(IEnumerable<string> externalCollection) : base(externalCollection)
{
_dataCollection = externalCollection as List<string> ?? new List<string>(externalCollection);
}
結合 readonly 與介面導向設計,可確保物件建構後狀態不可變,提升執行緒安全性。
| 反模式 | 問題 | 推薦模式 | 優勢 |
|---|---|---|---|
在 base() 中存取 this 欄位 | 編譯錯誤,違反建構順序 | 雙層建構子 (this() 鏈式呼叫) | 符合語言規範、單一實例來源、封裝良好 |
| 父類別建構後再設定屬性 | 破壞不可變性、多執行緒風險 | 建構子注入 + readonly | 物件一出生即完整、安全 |
掌握建構子鏈式呼叫技巧,是寫出健壯、可維護 .NET 類別庫的關鍵基石。