使用英语阅读

通过


Lazy<T> 类

定义

提供对延迟初始化的支持。

C#
public class Lazy<T>
C#
[System.Runtime.InteropServices.ComVisible(false)]
[System.Serializable]
public class Lazy<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创建线程安全的延迟初始值设定项:

C#
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);

工厂方法显示对象的创建,其中占位符用于进一步初始化:

C#
static LargeObject InitLargeObject()
{
    LargeObject large = new LargeObject(Thread.CurrentThread.ManagedThreadId);
    // Perform additional initialization here.
    return large;
}

请注意,前两个代码部分可以使用 lambda 函数进行组合,如下所示:

C#
lazyLargeObject = new Lazy<LargeObject>(() =>
{
    LargeObject large = new LargeObject(Thread.CurrentThread.ManagedThreadId);
    // Perform additional initialization here.
    return large;
});

该示例暂停,以指示在延迟初始化发生之前,不确定期可能已过。 按 Enter 键时,该示例将创建并启动三个线程。 所有三个线程使用的 ThreadProc 方法都会调用 Value 属性。 首次发生这种情况时,将创建 LargeObject 实例:

C#
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]);
}

LargeObject 类的构造函数(包括代码的最后一个关键部分)显示消息并记录初始化线程的标识。 程序输出显示在完整代码列表的末尾。

C#
int initBy = 0;
public LargeObject(int initializedBy)
{
    initBy = initializedBy;
    Console.WriteLine("LargeObject was created on thread id {0}.", initBy);
}

备注

为简单起见,此示例使用 Lazy<T>的全局实例,所有方法都 static(Visual Basic 中的Shared)。 这些不是使用延迟初始化的要求。

C#
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
 */

注解

使用延迟初始化来延迟创建大型或资源密集型对象,或者执行资源密集型任务,尤其是在程序生存期内可能不会发生此类创建或执行时。

若要准备延迟初始化,请创建 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 属性的字符串表示形式。

适用于

产品 版本
.NET Core 1.0, Core 1.1, Core 2.0, Core 2.1, Core 2.2, Core 3.0, Core 3.1, 5, 6, 7, 8, 9
.NET Framework 4.0, 4.5, 4.5.1, 4.5.2, 4.6, 4.6.1, 4.6.2, 4.7, 4.7.1, 4.7.2, 4.8, 4.8.1
.NET Standard 1.0, 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 2.0, 2.1
UWP 10.0

线程安全性

默认情况下,Lazy<T> 类的所有公共和受保护成员都是线程安全的,并且可以从多个线程并发使用。 可以使用类型构造函数的参数(可选)和每个实例删除这些线程安全保证。

另请参阅