延遲加載** 又稱延遲初始化,核心思想:**「用到時才創建」**。將昂貴資源(大物件、資料庫查詢、網絡連接、複雜計算)的初始化推遲到首次訪問時刻,提升啟動速度、降低記憶體佔用、避免無效計算。
Lazy<T>線程安全、支援異常緩存、可自定義初始化邏輯的標準實現。
// 延遲初始化字串:工廠委托在首次訪問 Value 時執行
Lazy lazyConnStr = new Lazy(() => {
Console.WriteLine("正在讀取配置...");
return ConfigurationManager.ConnectionStrings["MainDB"].ConnectionString;
}, LazyThreadSafetyMode.ExecutionAndPublication); // 預設:全線程安全
// 首次訪問觸發初始化
Console.WriteLine(lazyConnStr.IsValueCreated); // False
var connStr = lazyConnStr.Value; // 這裡執行工廠委托
Console.WriteLine(lazyConnStr.IsValueCreated); // True
// 後續訪問直接返回緩存值,不再執行委托
| 模式 | 行為 | 適用場景 |
|---|---|---|
ExecutionAndPublication (預設) | 雙重檢查鎖,確保工廠委托**僅執行一次**,所有線程見到同一實例 | 通用、預設推薦 |
PublicationOnly | 允許併發執行多次委托,**首個完成者勝出**,其餘丟棄 | 委托極輕量、無副作用、追求極致併發 |
None | **非線程安全**,單線程或外部同步場景 | 高頻單線程、性能極致敏感 |
public class BlogUser {
public int Id { get; }
public string Name { get; set; }
// 延遲加載文章列表:首次訪問時查詢資料庫
public Lazy> Articles { get; }
public BlogUser(int id, string name) {
Id = id; Name = name;
Articles = new Lazy>(
() => ArticleService.GetByUserId(Id), // 捕獲 Id 形成閉包
LazyThreadSafetyMode.ExecutionAndPublication);
}
}
public class ArticleService {
public static List GetByUserId(int userId) {
Console.WriteLine($"[DB] 查詢用戶 {userId} 的文章...");
// 模擬 DB 查詢
return new List {
new Article { Id = 1, Title = "Lazy Load", Date = DateTime.Parse("2024-01-15") },
new Article { Id = 2, Title = "Delegate", Date = DateTime.Parse("2024-01-16") }
};
}
}
// 使用
var user = new BlogUser(1, "張三");
Console.WriteLine("用戶創建完成,尚未查詢文章");
var firstTitle = user.Articles.Value[0].Title; // 這裡觸發 DB 查詢
Console.WriteLine($"首篇文章:{firstTitle}");
// 再次訪問不查詢 DB
var count = user.Articles.Value.Count;
public sealed class ReportEngine {
private static readonly Lazy _instance =
new Lazy(() => new ReportEngine(), LazyThreadSafetyMode.ExecutionAndPublication);
public static ReportEngine Instance => _instance.Value;
private ReportEngine() {
// 載入模板、初始化引擎、預熱字體...
Console.WriteLine("ReportEngine 初始化完成");
}
public void GenerateReport() { /* ... */ }
}
// 首次調用 Instance 時才初始化
ReportEngine.Instance.GenerateReport();
ASP.NET Core 內建 DI 容器支援 `Lazy
// 註冊
services.AddScoped();
// 控制器構造函數注入 Lazy
public class HomeController : Controller {
private readonly Lazy _heavy;
public HomeController(Lazy heavy) => _heavy = heavy;
public IActionResult Index() {
// 只有調用 _heavy.Value 時才會從容器解析 HeavyService
return View(_heavy.Value.GetData());
}
}
避免了「注入即實例化」導致的啟動效能抖動。
AsyncLazy<T> 異步初始化標準 `Lazy
// 簡易版 AsyncLazy
public class AsyncLazy {
private readonly Lazy> _lazy;
public AsyncLazy(Func> factory) {
_lazy = new Lazy>(() => factory(), LazyThreadSafetyMode.ExecutionAndPublication);
}
public TaskAwaiter GetAwaiter() => _lazy.Value.GetAwaiter();
public bool IsValueCreated => _lazy.IsValueCreated;
}
// 使用
var asyncLazy = new AsyncLazy(() => ConnectAsync());
await asyncLazy; // 異步等待初始化完成
| 場景 | 是否適合 Lazy | 理由 |
|---|---|---|
| 啟動時加載大型配置/緩存 | ✅ 是 | 縮短啟動時間,按需加載 |
| 導航屬性可能不被訪問 | ✅ 是 | 避免 N+1 查詢或無效查詢 |
| 單例服務構造昂貴 | ✅ 是 | 首次使用才付出成本 |
| 必須在啟動階段完成初始化 (預熱) | ❌ 否 | 違背預熱目的 |
| 初始化有副作用且需確定執行順序 | ❌ 否 | 延遲導致順序不可控 |
| 多線程併發初始化需合併 (去抖) | ⚠️ 慎用 | 用 `PublicationOnly` 或自定義鎖 |
`Lazy