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) 构造函数(为 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> 类的新实例,该实例使用 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> 类的所有公共和受保护成员都是线程安全的,并且可以从多个线程并发使用。 可以使用类型构造函数的参数(可选)和每个实例删除这些线程安全保证。

另请参阅