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
类,该类将由多个线程之一延迟初始化。 代码的四个关键部分说明了如何创建初始值设定项、工厂方法、实际初始化和 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)
工厂方法显示对象的创建,其中占位符用于进一步初始化:
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> 对象线程安全不会保护延迟初始化的对象。 如果多个线程可以访问延迟初始化的对象,则必须使其属性和方法安全进行多线程访问。
延迟初始化是否需要大量代码,或者延迟初始化的对象是否具有无参数构造函数,该构造函数需要执行所需的一切操作,并且不会引发异常? 如果需要编写初始化代码或需要处理异常,请使用采用工厂方法的构造函数之一。 在工厂方法中编写初始化代码。
下表根据以下两个因素显示了要选择的构造函数:
对象将由 | 如果不需要初始化代码(无参数构造函数),请使用 | 如果需要初始化代码,请使用 |
---|---|---|
多个线程 | Lazy<T>() | Lazy<T>(Func<T>) |
一个线程 |
Lazy<T>(Boolean),isThreadSafe 设置为 false 。 |
Lazy<T>(Func<T>, Boolean),isThreadSafe 设置为 false 。 |
可以使用 lambda 表达式来指定工厂方法。 这会将所有初始化代码保留在一个位置。 lambda 表达式捕获上下文,包括传递给延迟初始化对象的构造函数的任何参数。
异常缓存 使用工厂方法时,将缓存异常。 也就是说,如果在线程首次尝试访问 Lazy<T> 对象的 Value 属性时,工厂方法将引发异常,则每次后续尝试时都会引发相同的异常。 这可确保每次调用 Value 属性都会生成相同的结果,并避免在不同线程获得不同结果时可能出现的细微错误。
Lazy<T> 代表一个实际 T
,否则在一些早期点(通常是在启动期间)初始化的。 此时的失败通常是致命的。 如果可能存在可恢复的故障,我们建议将重试逻辑构建到初始化例程(在本例中为工厂方法),就像不使用延迟初始化一样。
锁定 在某些情况下,你可能希望避免 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时,即使指定工厂方法,也永远不会缓存异常。
等效构造函数 除了启用 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> 类的所有公共和受保护成员都是线程安全的,并且可以从多个线程并发使用。 可以使用类型构造函数的参数(可选)和每个实例删除这些线程安全保证。