Lazy 是 C# 中的一個泛型類別,它提供了一種方式來進行延遲初始化,即在需要使用之前不會初始化它。

可能的兩個使用情境:

  1. 當建立某個物件會耗費大量資源,而程式可能不使用它時。
  2. 當建立某個物件會耗費大量資源,而您想要延遲到完成其他耗費資源的作業後,再建立它。

Lazy 簡單範例:

using System;
using System.Threading;

class Program
{
    static void Main()
    {
        Lazy<string> lazyString = new Lazy<string>(() =>
        {
            Console.WriteLine("執行A計算...");
            Thread.Sleep(2000);
            return "這是A計算結果";
        });

        Console.WriteLine("執行B計算...");

        // 等待兩秒
        Thread.Sleep(2000);

        // 使用 Lazy<T> 的值
        Console.WriteLine(lazyString.Value);

        Console.ReadLine();
    }
}

在此範例中,我們創建了一個 Lazy 物件,該物件會在需要時執行計算。在 Main 方法的開始部分,我們不會立即初始化這個物件,而是先執行一些其他計算。接下來,我們等待兩秒後,才呼叫 lazyString.Value,這時才會執行該物件的初始化,並且返回計算的結果。

當我們執行這個範例時,它的輸出應該會如下所示:

執行B計算...
執行A計算...
這是A計算結果

可以看到, Lazy 物件的初始化被推遲到了需要使用其值時才執行。

值得注意的是, Lazy 的初始化只會發生一次,即使它被多次使用。這是因為 Lazy 使用了一個稱為“double-checked locking”的技術,它可以確保只有一個執行緒在初始化期間可以訪問該物件。如果多個執行緒同時嘗試訪問該物件,它們會在等待 Lazy 的初始化完成之前被阻塞。

Lazy Thread safe範例

以下是一個使用 Lazy 創建單例模式的範例,並確保它是 thread safe 的:

public class Singleton
{
    private static readonly Lazy<Singleton> instance = new Lazy<Singleton>(() => new Singleton(), LazyThreadSafetyMode.ExecutionAndPublication);

    private Singleton()
    {
        // 建構子為 private,以防止從外部實例化
    }

    public static Singleton Instance
    {
        get { return instance.Value; }
    }
}

在上面的範例中,我們使用 Lazy 創建 Singleton 類別的單例實例。由於 Lazy 預設是執行緒安全的,但建議指定 LazyThreadSafetyMode.ExecutionAndPublication 以確保在初始化期間和初始化完成後都是執行緒安全的。因此,即使有多個執行緒同時訪問 Singleton.Instance 屬性,也不會導致多個實例被創建。

值得注意的是, Lazy 並不能解決所有的 thread safety 問題。在某些情況下,我們可能需要使用其他同步機制,例如 lock 關鍵字。

Lazy 的一些常用屬性和方法:

  1. Value:用於獲取 Lazy 的值,如果 Lazy 還沒有初始化,則在第一次呼叫時會進行初始化。

  2. IsValueCreated:用於檢查 Lazy 是否已經初始化。

  3. LazyThreadSafetyMode:用於指定 Lazy 的執行緒安全性,有三個可選值:

  • None:Lazy 不是執行緒安全的,這意味著多個執行緒可以同時訪問它,並導致一些問題,建議只有在確定只有一個執行緒會訪問該物件時使用此選項。
  • PublicationOnly:Lazy 在初始化完成後變為執行緒安全,這意味著在初始化期間可能會有多個執行緒同時訪問該物件。
  • ExecutionAndPublication:Lazy 在初始化期間和初始化完成後都是執行緒安全的,這意味著在初始化期間只有一個執行緒可以訪問該物件。
  1. Lazy()建構子:用於創建 Lazy 物件,接受一個 Func 委託,該委託將在需要時執行以初始化 Lazy

如上述範例中,我們使用了 Lambda 表達式定義了一個 Func 委託,它將在需要時初始化 Lazy

具體來說,這個委託創建了一個新的 Singleton 物件,並將其作為 Lazy 的值返回。

小結

使用 Lazy 可以讓我們在需要時才執行昂貴的操作,從而提高應用程式的效能。然而,如果我們的程式需要經常訪問 Lazy 的值,則可能會導致不必要的延遲和效能問題。因此,在使用 Lazy 時需要注意適當的使用情境,以避免出現效能問題。