共用方式為


Lazy<T> 類別

定義

提供延遲初始化的支援。

generic <typename T>
public ref class Lazy
public class Lazy<T>
[System.Runtime.InteropServices.ComVisible(false)]
[System.Serializable]
public class Lazy<T>
type Lazy<'T> = class
[<System.Runtime.InteropServices.ComVisible(false)>]
[<System.Serializable>]
type Lazy<'T> = class
Public Class Lazy(Of T)

類型參數

T

要延遲初始化的物件型別。

繼承
Lazy<T>
衍生
屬性

範例

下列範例示範如何使用 Lazy<T> 類別來提供延遲初始化,以及從多個線程存取。

注意

此範例會使用 Lazy<T>(Func<T>) 建構函式。 它也示範如何使用 Lazy<T>(Func<T>, Boolean) 建構函式(指定 isThreadSafetrue),以及 Lazy<T>(Func<T>, LazyThreadSafetyMode) 建構函式(指定 modeLazyThreadSafetyMode.ExecutionAndPublication)。 若要切換至不同的建構函式,只要變更批注的建構函式即可。

如需示範使用相同建構函式進行例外狀況快取的範例,請參閱 Lazy<T>(Func<T>) 建構函式。

此範例會定義一個 LargeObject 類別,這個類別將由數個線程之一初始化。 程序代碼的四個主要區段說明如何建立初始化運算式、Factory 方法、實際初始化,以及 LargeObject 類別的建構函式,以在建立對象時顯示訊息。 在 Main 方法的開頭,此範例會為 LargeObject建立安全線程延遲初始化表達式:

lazyLargeObject = new Lazy<LargeObject>(InitLargeObject);

// The following lines show how to use other constructors to achieve exactly the
// same result as the previous line:
//lazyLargeObject = new Lazy<LargeObject>(InitLargeObject, true);
//lazyLargeObject = new Lazy<LargeObject>(InitLargeObject,
//                               LazyThreadSafetyMode.ExecutionAndPublication);
let lazyLargeObject = Lazy<LargeObject> initLargeObject

// The following lines show how to use other constructors to achieve exactly the
// same result as the previous line:
//     let lazyLargeObject = Lazy<LargeObject>(initLargeObject, true)
//     let lazyLargeObject = Lazy<LargeObject>(initLargeObject,
//                               LazyThreadSafetyMode.ExecutionAndPublication)
lazyLargeObject = New Lazy(Of LargeObject)(AddressOf InitLargeObject)

' The following lines show how to use other constructors to achieve exactly the
' same result as the previous line: 
'lazyLargeObject = New Lazy(Of LargeObject)(AddressOf InitLargeObject, True)
'lazyLargeObject = New Lazy(Of LargeObject)(AddressOf InitLargeObject, _
'                               LazyThreadSafetyMode.ExecutionAndPublication)

Factory 方法會顯示物件的建立,並具有進一步初始化的佔位符:

static LargeObject InitLargeObject()
{
    LargeObject large = new LargeObject(Thread.CurrentThread.ManagedThreadId);
    // Perform additional initialization here.
    return large;
}
let initLargeObject () =
    let large = LargeObject Thread.CurrentThread.ManagedThreadId
    // Perform additional initialization here.
    large
Private Shared Function InitLargeObject() As LargeObject
    Dim large As New LargeObject(Thread.CurrentThread.ManagedThreadId)
    ' Perform additional initialization here.
    Return large
End Function

請注意,前兩個程式代碼區段可以使用 Lambda 函式來合併,如下所示:

lazyLargeObject = new Lazy<LargeObject>(() =>
{
    LargeObject large = new LargeObject(Thread.CurrentThread.ManagedThreadId);
    // Perform additional initialization here.
    return large;
});
let lazyLargeObject = Lazy<LargeObject>(fun () ->
    let large = LargeObject Thread.CurrentThread.ManagedThreadId
    // Perform additional initialization here.
    large)
lazyLargeObject = New Lazy(Of LargeObject)(Function () 
    Dim large As New LargeObject(Thread.CurrentThread.ManagedThreadId) 
    ' Perform additional initialization here.
    Return large
End Function)

此範例會暫停,表示不定期間可能會在延遲初始化發生之前經過。 當您按下 Enter 鍵時,此範例會建立並啟動三個線程。 這三個線程所使用的 ThreadProc 方法會呼叫 Value 屬性。 第一次發生這種情況時,會建立 LargeObject 實例:

LargeObject large = lazyLargeObject.Value;

// IMPORTANT: Lazy initialization is thread-safe, but it doesn't protect the
//            object after creation. You must lock the object before accessing it,
//            unless the type is thread safe. (LargeObject is not thread safe.)
lock(large)
{
    large.Data[0] = Thread.CurrentThread.ManagedThreadId;
    Console.WriteLine("Initialized by thread {0}; last used by thread {1}.",
        large.InitializedBy, large.Data[0]);
}
let large = lazyLargeObject.Value

// IMPORTANT: Lazy initialization is thread-safe, but it doesn't protect the
//            object after creation. You must lock the object before accessing it,
//            unless the type is thread safe. (LargeObject is not thread safe.)
lock large (fun () ->
    large.Data[0] <- Thread.CurrentThread.ManagedThreadId
    printfn $"Initialized by thread {large.InitializedBy} last used by thread {large.Data[0]}.")
Dim large As LargeObject = lazyLargeObject.Value

' IMPORTANT: Lazy initialization is thread-safe, but it doesn't protect the  
'            object after creation. You must lock the object before accessing it,
'            unless the type is thread safe. (LargeObject is not thread safe.)
SyncLock large
    large.Data(0) = Thread.CurrentThread.ManagedThreadId
    Console.WriteLine("Initialized by thread {0}; last used by thread {1}.", _
        large.InitializedBy, large.Data(0))
End SyncLock

包含程式代碼最後一個索引鍵區段的 LargeObject 類別建構函式會顯示訊息,並記錄初始化線程的身分識別。 程序輸出會出現在完整程式代碼清單的結尾。

int initBy = 0;
public LargeObject(int initializedBy)
{
    initBy = initializedBy;
    Console.WriteLine("LargeObject was created on thread id {0}.", initBy);
}
type LargeObject(initBy) =
    do 
        printfn $"LargeObject was created on thread id %i{initBy}."
Private initBy As Integer = 0
Public Sub New(ByVal initializedBy As Integer)
    initBy = initializedBy
    Console.WriteLine("LargeObject was created on thread id {0}.", initBy)
End Sub

注意

為了簡單起見,此範例會使用全域實例 Lazy<T>,而且所有方法都會 static(Visual Basic 中的Shared)。 這些不是使用延遲初始化的需求。

using System;
using System.Threading;

class Program
{
    static Lazy<LargeObject> lazyLargeObject = null;

    static LargeObject InitLargeObject()
    {
        LargeObject large = new LargeObject(Thread.CurrentThread.ManagedThreadId);
        // Perform additional initialization here.
        return large;
    }

    static void Main()
    {
        // The lazy initializer is created here. LargeObject is not created until the
        // ThreadProc method executes.
        lazyLargeObject = new Lazy<LargeObject>(InitLargeObject);

        // The following lines show how to use other constructors to achieve exactly the
        // same result as the previous line:
        //lazyLargeObject = new Lazy<LargeObject>(InitLargeObject, true);
        //lazyLargeObject = new Lazy<LargeObject>(InitLargeObject,
        //                               LazyThreadSafetyMode.ExecutionAndPublication);

        Console.WriteLine(
            "\r\nLargeObject is not created until you access the Value property of the lazy" +
            "\r\ninitializer. Press Enter to create LargeObject.");
        Console.ReadLine();

        // Create and start 3 threads, each of which uses LargeObject.
        Thread[] threads = new Thread[3];
        for (int i = 0; i < 3; i++)
        {
            threads[i] = new Thread(ThreadProc);
            threads[i].Start();
        }

        // Wait for all 3 threads to finish.
        foreach (Thread t in threads)
        {
            t.Join();
        }

        Console.WriteLine("\r\nPress Enter to end the program");
        Console.ReadLine();
    }

    static void ThreadProc(object state)
    {
        LargeObject large = lazyLargeObject.Value;

        // IMPORTANT: Lazy initialization is thread-safe, but it doesn't protect the
        //            object after creation. You must lock the object before accessing it,
        //            unless the type is thread safe. (LargeObject is not thread safe.)
        lock(large)
        {
            large.Data[0] = Thread.CurrentThread.ManagedThreadId;
            Console.WriteLine("Initialized by thread {0}; last used by thread {1}.",
                large.InitializedBy, large.Data[0]);
        }
    }
}

class LargeObject
{
    public int InitializedBy { get { return initBy; } }

    int initBy = 0;
    public LargeObject(int initializedBy)
    {
        initBy = initializedBy;
        Console.WriteLine("LargeObject was created on thread id {0}.", initBy);
    }

    public long[] Data = new long[100000000];
}

/* This example produces output similar to the following:

LargeObject is not created until you access the Value property of the lazy
initializer. Press Enter to create LargeObject.

LargeObject was created on thread id 3.
Initialized by thread 3; last used by thread 3.
Initialized by thread 3; last used by thread 4.
Initialized by thread 3; last used by thread 5.

Press Enter to end the program
 */
open System
open System.Threading

type LargeObject(initBy) =
    do 
        printfn $"LargeObject was created on thread id %i{initBy}."
    member _.InitializedBy = initBy
    member val Data = Array.zeroCreate<int64> 100000000

let initLargeObject () =
    let large = LargeObject Thread.CurrentThread.ManagedThreadId
    // Perform additional initialization here.
    large

// The lazy initializer is created here. LargeObject is not created until the
// ThreadProc method executes.
let lazyLargeObject = Lazy<LargeObject> initLargeObject

// The following lines show how to use other constructors to achieve exactly the
// same result as the previous line:
//     let lazyLargeObject = Lazy<LargeObject>(initLargeObject, true)
//     let lazyLargeObject = Lazy<LargeObject>(initLargeObject,
//                               LazyThreadSafetyMode.ExecutionAndPublication)

let threadProc (state: obj) =
    let large = lazyLargeObject.Value

    // IMPORTANT: Lazy initialization is thread-safe, but it doesn't protect the
    //            object after creation. You must lock the object before accessing it,
    //            unless the type is thread safe. (LargeObject is not thread safe.)
    lock large (fun () ->
        large.Data[0] <- Thread.CurrentThread.ManagedThreadId
        printfn $"Initialized by thread {large.InitializedBy} last used by thread {large.Data[0]}.")

printfn """
LargeObject is not created until you access the Value property of the lazy
initializer. Press Enter to create LargeObject."""
stdin.ReadLine() |> ignore

// Create and start 3 threads, each of which uses LargeObject.

let threads = Array.zeroCreate 3
for i = 0 to 2 do
    threads[i] <- Thread(ParameterizedThreadStart threadProc)
    threads[i].Start()

// Wait for all 3 threads to finish.
for t in threads do
    t.Join()

printfn "\nPress Enter to end the program"
stdin.ReadLine() |> ignore

// This example produces output similar to the following:
//     LargeObject is not created until you access the Value property of the lazy
//     initializer. Press Enter to create LargeObject.
//     
//     LargeObject was created on thread id 3.
//     Initialized by thread 3 last used by thread 3.
//     Initialized by thread 3 last used by thread 4.
//     Initialized by thread 3 last used by thread 5.
//     
//     Press Enter to end the program
Imports System.Threading

Friend Class Program
    Private Shared lazyLargeObject As Lazy(Of LargeObject) = Nothing

    Private Shared Function InitLargeObject() As LargeObject
        Dim large As New LargeObject(Thread.CurrentThread.ManagedThreadId)
        ' Perform additional initialization here.
        Return large
    End Function


    Shared Sub Main()
        ' The lazy initializer is created here. LargeObject is not created until the 
        ' ThreadProc method executes.
        lazyLargeObject = New Lazy(Of LargeObject)(AddressOf InitLargeObject)

        ' The following lines show how to use other constructors to achieve exactly the
        ' same result as the previous line: 
        'lazyLargeObject = New Lazy(Of LargeObject)(AddressOf InitLargeObject, True)
        'lazyLargeObject = New Lazy(Of LargeObject)(AddressOf InitLargeObject, _
        '                               LazyThreadSafetyMode.ExecutionAndPublication)


        Console.WriteLine(vbCrLf & _
            "LargeObject is not created until you access the Value property of the lazy" _
            & vbCrLf & "initializer. Press Enter to create LargeObject.")
        Console.ReadLine()

        ' Create and start 3 threads, each of which uses LargeObject.
        Dim threads(2) As Thread
        For i As Integer = 0 To 2
            threads(i) = New Thread(AddressOf ThreadProc)
            threads(i).Start()
        Next i

        ' Wait for all 3 threads to finish. 
        For Each t As Thread In threads
            t.Join()
        Next t

        Console.WriteLine(vbCrLf & "Press Enter to end the program")
        Console.ReadLine()
    End Sub


    Private Shared Sub ThreadProc(ByVal state As Object)
        Dim large As LargeObject = lazyLargeObject.Value

        ' IMPORTANT: Lazy initialization is thread-safe, but it doesn't protect the  
        '            object after creation. You must lock the object before accessing it,
        '            unless the type is thread safe. (LargeObject is not thread safe.)
        SyncLock large
            large.Data(0) = Thread.CurrentThread.ManagedThreadId
            Console.WriteLine("Initialized by thread {0}; last used by thread {1}.", _
                large.InitializedBy, large.Data(0))
        End SyncLock
    End Sub
End Class

Friend Class LargeObject
    Public ReadOnly Property InitializedBy() As Integer
        Get
            Return initBy
        End Get
    End Property

    Private initBy As Integer = 0
    Public Sub New(ByVal initializedBy As Integer)
        initBy = initializedBy
        Console.WriteLine("LargeObject was created on thread id {0}.", initBy)
    End Sub

    Public Data(99999999) As Long
End Class

' This example produces output similar to the following:
'
'LargeObject is not created until you access the Value property of the lazy
'initializer. Press Enter to create LargeObject.
'
'LargeObject was created on thread id 3.
'Initialized by thread 3; last used by thread 3.
'Initialized by thread 3; last used by thread 5.
'Initialized by thread 3; last used by thread 4.
'
'Press Enter to end the program
'

備註

使用延遲初始化來延遲建立大型或耗用大量資源的物件,或執行需要大量資源的工作,特別是在程式存留期間可能不會發生這類建立或執行時。

若要準備延遲初始化,請建立 Lazy<T>實例。 您所建立 Lazy<T> 物件的 type 自變數會指定您要延遲初始化的物件類型。 您用來建立 Lazy<T> 物件的建構函式會決定初始化的特性。 第一次存取 Lazy<T>.Value 屬性時,就會發生延遲初始化。

在大部分情況下,選擇建構函式取決於您對兩個問題的解答:

  • 從多個線程存取延遲初始化的物件嗎? 如果是,Lazy<T> 物件可能會在任何線程上建立它。 您可以使用其中一個簡單建構函式,其預設行為是建立安全線程 Lazy<T> 物件,因此不論有多少線程嘗試存取該物件,都只會建立一個延遲具現化對象的實例。 若要建立非線程安全的 Lazy<T> 物件,您必須使用可讓您指定無線程安全性的建構函式。

    謹慎

    Lazy<T> 物件線程安全不會保護延遲初始化的物件。 如果多個線程可以存取延遲初始化的物件,您必須讓其屬性和方法安全進行多線程存取。

  • 延遲初始化是否需要大量程式碼,或延遲初始化的物件是否有無參數建構函式,您需要的所有建構函式,而且不會擲回例外狀況? 如果您需要撰寫初始化程序代碼,或需要處理例外狀況,請使用採用 Factory 方法的其中一個建構函式。 在 Factory 方法中撰寫初始化程序代碼。

下表根據下列兩個因素顯示要選擇的建構函式:

物件將會由存取 如果不需要初始化程式代碼(無參數建構函式),請使用 如果需要初始化程序代碼,請使用
多個線程 Lazy<T>() Lazy<T>(Func<T>)
一個線程 Lazy<T>(Boolean),且 isThreadSafe 設定為 false Lazy<T>(Func<T>, Boolean),且 isThreadSafe 設定為 false

您可以使用 Lambda 運算式來指定 Factory 方法。 這會將所有初始化程序代碼保留在一個位置。 Lambda 運算式會擷取內容,包括您傳遞至延遲初始化物件建構函式的任何自變數。

例外狀況快取 當您使用 Factory 方法時,會快取例外狀況。 也就是說,如果處理站方法第一次嘗試存取 Lazy<T> 物件的 Value 屬性時擲回例外狀況,則每次後續嘗試都會擲回相同的例外狀況。 這可確保每次呼叫 Value 屬性都會產生相同的結果,並避免在不同線程取得不同結果時可能發生的細微錯誤。 Lazy<T> 代表實際 T,否則通常會在啟動期間於某個時間點初始化。 先前的失敗通常是致命的。 如果可能發生可復原的失敗,建議您在初始化例程中建置重試邏輯(在此案例中為 Factory 方法),就像您不是使用延遲初始化一樣。

鎖定 在某些情況下,您可能想要避免 Lazy<T> 對象默認鎖定行為的額外負荷。 在罕見的情況下,可能有死結的可能性。 在這種情況下,您可以使用 Lazy<T>(LazyThreadSafetyMode)Lazy<T>(Func<T>, LazyThreadSafetyMode) 建構函式,並指定 LazyThreadSafetyMode.PublicationOnly。 這可讓 Lazy<T> 物件在線程同時呼叫 Value 屬性時,在每個線程上建立延遲初始化對象的複本。 Lazy<T> 對象可確保所有線程都使用相同的延遲初始化物件實例,並捨棄未使用的實例。 因此,降低鎖定額外負荷的成本是,您的程式有時可能會建立和捨棄昂貴對象的額外複本。 在大部分情況下,這不太可能。 Lazy<T>(LazyThreadSafetyMode)Lazy<T>(Func<T>, LazyThreadSafetyMode) 建構函式的範例會示範此行為。

重要

當您指定 LazyThreadSafetyMode.PublicationOnly時,即使指定 Factory 方法,也不會快取例外狀況。

對等建構函式 除了啟用 LazyThreadSafetyMode.PublicationOnly之外,Lazy<T>(LazyThreadSafetyMode)Lazy<T>(Func<T>, LazyThreadSafetyMode) 建構函式還可以複製其他建構函式的功能。 下表顯示產生對等行為的參數值。

建立 Lazy<T> 物件 對於具有 LazyThreadSafetyModemode 參數的建構函式,請將 mode 設定為 針對具有布爾值 isThreadSafe 參數的建構函式,請將 isThreadSafe 設定為 對於沒有線程安全性參數的建構函式
完全安全線程;會使用鎖定來確保只有一個線程會初始化值。 ExecutionAndPublication true 所有這類建構函式都是完全安全線程。
不是安全線程。 None false 不適用。
完全安全線程;線程競爭以初始化值。 PublicationOnly 不適用。 不適用。

其他功能 如需搭配線程靜態字段使用 Lazy<T> 的相關信息,或作為屬性的備份存放區,請參閱 延遲初始化

建構函式

Lazy<T>()

初始化 Lazy<T> 類別的新實例。 發生延遲初始化時,會使用目標類型的無參數建構函式。

Lazy<T>(Boolean)

初始化 Lazy<T> 類別的新實例。 發生延遲初始化時,會使用目標型別的無參數建構函式和指定的初始化模式。

Lazy<T>(Func<T>)

初始化 Lazy<T> 類別的新實例。 發生延遲初始化時,會使用指定的初始化函式。

Lazy<T>(Func<T>, Boolean)

初始化 Lazy<T> 類別的新實例。 發生延遲初始化時,會使用指定的初始化函式和初始化模式。

Lazy<T>(Func<T>, LazyThreadSafetyMode)

使用指定的初始化函式和線程安全模式,初始化 Lazy<T> 類別的新實例。

Lazy<T>(LazyThreadSafetyMode)

初始化 Lazy<T> 類別的新實例,這個實例會使用 T 的無參數建構函式和指定的線程安全模式。

Lazy<T>(T)

初始化 Lazy<T> 類別的新實例,這個實例使用預先初始化的指定值。

屬性

IsValueCreated

取得值,這個值表示是否已為此 Lazy<T> 實例建立值。

Value

取得目前 Lazy<T> 實例的延遲初始化值。

方法

Equals(Object)

判斷指定的物件是否等於目前的物件。

(繼承來源 Object)
GetHashCode()

做為預設哈希函式。

(繼承來源 Object)
GetType()

取得目前實例的 Type

(繼承來源 Object)
MemberwiseClone()

建立目前 Object的淺層複本。

(繼承來源 Object)
ToString()

建立並傳回這個實例之 Value 屬性的字串表示。

適用於

執行緒安全性

根據預設,Lazy<T> 類別的所有公用和受保護成員都是安全線程,而且可以從多個線程同時使用。 您可以使用型別建構函式的參數,選擇性地移除這些線程安全性保證。

另請參閱