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) 針對) 指定 isThreadSafe true 建構函式 (,以及 Lazy<T>(Func<T>, LazyThreadSafetyMode) 針對) 指定的 LazyThreadSafetyMode.ExecutionAndPublication mode 建構函式 (。 若要切換至不同的建構函式,只要變更批註的建構函式即可。

如需示範使用相同建構函式進行例外狀況快取的範例,請參閱 建 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 方法時,會快取例外狀況。 也就是說,如果 Factory 方法在第一次嘗試存取 Value 物件的 屬性 Lazy<T> 時擲回例外狀況,則每次後續嘗試時都會擲回相同的例外狀況。 這可確保每次呼叫 Value 屬性都會產生相同的結果,並避免在不同執行緒取得不同結果時可能發生的細微錯誤。 Lazy<T>代表實際 T 的情況,否則通常會在啟動期間于某個時間點初始化。 該較早時間點的失敗通常是嚴重。 如果可能發生可復原的失敗,建議您在此案例中將重試邏輯建置到初始化常式 (,Factory 方法) ,就像您未使用延遲初始化一樣。

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

重要

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

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

若要建立 Lazy<T> 物件,該物件為 對於具有 LazyThreadSafetyMode mode 參數的建構函式,請將 設定 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> 都是安全線程,而且可以從多個執行緒同時使用。 您可以使用型別建構函式的參數,選擇性地移除這些執行緒安全性保證,而且每個實例都會移除。

另請參閱