首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> 關於C語言

C# 延遲加載 深度解析:Lazy<T> 原理、實戰模式與 ORM 應用場景

     編輯:關於C語言

什麼是延遲加載?

延遲加載** 又稱延遲初始化,核心思想:**「用到時才創建」**。將昂貴資源(大物件、資料庫查詢、網絡連接、複雜計算)的初始化推遲到首次訪問時刻,提升啟動速度、降低記憶體佔用、避免無效計算。

.NET 4.0+ 核心工具: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**非線程安全**,單線程或外部同步場景高頻單線程、性能極致敏感

實戰模式一:導航屬性延遲加載 (ORM 風格)

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` 不支援 `async` 工廠。高併發場景下異步初始化需自行實現或引用 `Nito.AsyncEx`:

// 簡易版 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` 或自定義鎖

ORM 框架中的延遲加載

  • Entity Framework Core:導航屬性預設**不**延遲加載(需顯式 `.Include()` 或配置 `UseLazyLoadingProxies` + `virtual` 代理)。
  • NHibernate:預設開啟延遲加載,透過動態代理攔截屬性訪問發 SQL。
  • Dapper:無延遲加載,手寫 `Lazy` 或 `QueryMultiple` 批量獲取。

避坑指南

  1. 異常緩存:工廠委托拋異常會被 `Lazy` 緩存,**後續訪問重拋同一異常**。需在委托內 `try/catch` 轉換為結果物件,或用 `AsyncLazy` 捕獲異常於 `Task` 中。
  2. 閉包捕獲變化變數:`new Lazy(() => Service.GetById(id))` 中 `id` 若為循環變數,需 `var captured = id;` 避免閉包陷阱。
  3. 循環依賴**:A `Lazy`,B `Lazy` 同時首次訪問可能死鎖(預設模式)。設計上避免循環引用。
  4. 序列化**:`Lazy` 本身可序列化,但反序列化後 `IsValueCreated` 為 `false`,再次訪問會**重新執行委托**。

結語

`Lazy` 是 .NET 框架級的延遲初始化標準解法,**線程安全、語義清晰、零依賴**。善用它優化啟動路徑、按需加載關聯數據、實現線程安全單例。記住:**「不確定是否會用、用時才創建」**,是延遲加載的靈魂。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved