在軟體開發領域,「延遲」指的是將高成本的操作推遲到真正需要時才執行。這不僅是一種編碼技巧,更是一種重要的設計模式,廣泛應用於極限編程(XP)的「你不會需要它」(YAGNI)原則、物件關聯對應(ORM)工具的延遲載入,以及各種資源密集型場景中。
在 .NET Framework 4 推出之前,開發人員必須自行實作延遲初始化邏輯,通常涉及雙重檢查鎖定、volatile 關鍵字等容易出錯的多線程同步代碼。這不僅增加了開發負擔,也埋下了競態條件的隱患。
System.Lazy<T> 為 .NET 開發者提供了標準化、線程安全的延遲初始化解決方案。它像一個智能工廠,包裝目標類型 T 的實例創建邏輯,僅在首次存取 Value 屬性時才真實例化物件,有效避免不必要的記憶體佔用與運算開銷,並能顯著加快應用程式啟動速度。
// 建立延遲初始化包裝器
var container = new Lazy<DataContainer>();
// 僅在首次存取 Value 時觸發實例化
Console.WriteLine(container.Value.SomeValue);
T 型別實例。首次存取時觸發初始化,後續存取直接返回快取實例。Value 的前置條件。Lazy<T> 預設採用 LazyThreadSafetyMode.ExecutionAndPublication 模式,確保多線程環境下只有一個線程執行初始化,所有線程共享同一結果。框架還提供三種模式供選擇:
| 模式 | 行為 | 適用場景 |
|---|---|---|
ExecutionAndPublication (預設) |
使用鎖確保單一線程初始化,完全線程安全 | 通用場景,安全性優先 |
None |
非線程安全,多線程存取行為未定義 | 極度追求效能、確保單線程存取的場景 |
PublicationOnly |
允許多線程並行初始化,首個完成者勝出,其他結果被丟棄 | 初始化成本低、可容忍重複運算的場景 |
建議:除非經過嚴格效能分析證明必要,否則請堅持使用預設的完全線程安全模式。
// 使用 Lambda 表達式傳遞建構參數
var orders = new Lazy<Orders>(() => new Orders(10));
委托僅在首次存取 Value 時執行,支援複雜的初始化邏輯。
若初始化過程拋出例外,該例外會被包裝為 TargetInvocationException 並快取。後續所有存取 Value 的嘗試都會重新拋出相同例外,確保行為確定性。
當需要每個線程擁有獨立實例時,ThreadLocal<T> 是正確選擇。它實作 IList<T>,Value 屬性可讀寫,各線程間資料完全隔離,互不干擾。
在非 ORM 的資料存取層、依賴注入容器、MEF 元件模型等場景,Lazy<T> 是實現延遲載入屬性的理想選擇:
public class Customer
{
private readonly Lazy<IList<Order>> _orders;
public Customer(string id)
{
_orders = new Lazy<IList<Order>>(() => LoadOrdersFromDatabase(id));
}
public IList<Order> Orders => _orders.Value; // 首次存取時載入
}
Lazy<T> 將延遲初始化從手工實作的錯誤陷阱中解放出來,提供標準化、高效能、線程安全的解決方案。合理運用其三種線程模式與自訂初始化委托,能在保證正確性的前提下最大化資源利用效率。