Lazy<T> 類別
定義
重要
部分資訊涉及發行前產品,在發行之前可能會有大幅修改。 Microsoft 對此處提供的資訊,不做任何明確或隱含的瑕疵擔保。
提供延遲初始化的支援。
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) 建構函式(指定 mode
的 LazyThreadSafetyMode.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> 物件 | 對於具有 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> 類別的新實例,這個實例會使用 |
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> 類別的所有公用和受保護成員都是安全線程,而且可以從多個線程同時使用。 您可以使用型別建構函式的參數,選擇性地移除這些線程安全性保證。