Lazy<T> 类
定义
重要
一些信息与预发行产品相关,相应产品在发行之前可能会进行重大修改。 对于此处提供的信息,Microsoft 不作任何明示或暗示的担保。
提供对延迟初始化的支持。
generic <typename T>
public ref class Lazypublic 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> = classPublic 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> 对象 | 对于具有 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> 类的新实例,该实例使用  | 
| 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> 类的所有公共和受保护成员都是线程安全的,并且可以从多个线程并发使用。 可以使用类型构造函数的参数(可选)和每个实例删除这些线程安全保证。