40

LazyInitializerLazy<T>类之间有什么区别?我知道他们都只会按需初始化对象。我什么时候需要使用它们中的每一个?

4

7 回答 7

43

我不确定你是否还在研究这个,但我不得不深入研究两者的细节,Lazy<T>最近LazyInitializer.EnsureInitialized<T>(),所以我想我应该分享我的发现。

首先,一些数字。我使用这两种方法对 1000 万个值的批次运行基准测试,测试内存使用情况GC.GetTotalMemory(true)并获取Stopwatch实例化、第一个值访问和后续值访问的时间:

Lazy<T> Memory Use:                  320,000,000 bytes (32B/instance)
EnsureInitialized<T>() Memory Use:   N/A

Lazy<T> Instantiation Time:          622.01 ms
EnsureInitialized<T>() Inst. Time:   N/A

Lazy<T> First Access:                1,373.50 ms
EnsureInitialized<T>() First Access: 72.94 ms

Lazy<T> Subsequent Accesses:         18.51 ms
EnsureInitialized<T>() Subsequent:   13.75 ms

(我使用LazyThreadSafetyMode.PublicationOnly了,这看起来与默认情况下Lazy<T>'s采用的线程安全方法相同。)LazyInitializer

正如你所看到的,除非我以某种方式搞砸了我的测试(永远不会出错!),在这种情况下LazyInitializer,几乎所有可量化的方式都是优越的。它没有内存或实例化开销,而且创建和检索值都更快。

那么,为什么要使用Lazy<T>? 嗯,首先,这些是我在 x64 系统上的测试结果,在其他情况下你可能会得到不同的结果。

Lazy<T>也可以产生更清晰、更简洁的代码。 return myLazy.Value;比友好得多return LazyInitializer.EnsureInitialized(ref myValue, () => GetValue(foo));

此外,Lazy<T>如果您正在处理值类型或可能合法为null. 使用LazyInitializer时,您必须使用第二个布尔字段来跟踪值是否已初始化,从而加剧了代码清晰度问题。 Lazy<T>如果您想要更严格的线程安全性,使用起来也更简单。

从总体上看,对于很多应用程序来说,大部分开销可能可以忽略不计(尽管并非总是如此——我开始研究这个的原因是因为我正在处理一个涉及数百万非常小的延迟加载值的应用程序,并且每个实例 32 字节的开销Lazy<T>实际上开始变得不方便)。

最后,除非您的应用程序非常占用内存,否则我认为这通常取决于个人喜好。对于非空引用类型,我个人认为LazyInitializer.EnsureInitialized<T>()是一种更优雅的方法,但我也可以挖掘代码清晰度参数。

于 2013-04-20T05:02:13.613 回答
20

Lazy<T>( MSDN ) 是一个通用包装器,它允许通过持有工厂方法 ( ) 并在访问属性 getter时调用它T来按需创建实例。TFunc<T>Value

LazyInitializer- 具有一组静态方法的静态类,这只是一个使用Activator.CreateInstance()(反射)能够实例化给定类型实例的助手。它不保留任何本地私有字段,也不公开任何属性,因此没有内存使用开销。

值得注意的是,这两个类都Func<T>用作实例工厂。

MSDNLazyInitializer关于类的几句话:

这些例程避免需要分配专用的延迟初始化实例,而是使用引用来确保在访问目标时已对其进行初始化。

PS:我发现了一种有趣的方法来LazyIntiializer检查实例是否已经初始化,它只是将传入的引用与 a 进行比较default(T),很好:

private static T EnsureInitializedCore<T>(ref T target, Func<T> valueFactory) 
    where T : class
{
    T t = valueFactory();
    if (t == null)
    {
       throw new InvalidOperationException(Environment.GetResourceString("Lazy_StaticInit_InvalidOperation"));
    }

    Interlocked.CompareExchange<T>(ref target, t, default(T));
    return target;
}

对我来说似乎很奇怪,它每次在实际检查之前都会创建一个新实例:

T t = valueFactory(); 
// ... and only then does check
于 2012-08-17T14:21:18.393 回答
4

正如其他答案所说,

Lazy<T>

  • 通常会提供更简洁的代码:只需在您访问它的任何地方进行初始化x = new Lazy<T>(_ => new ...)和使用。x.Value

  • Value如果多个线程同时访问未初始化Lazy<T>对象的属性,则允许使用不同的预定义选项来处理初始化和异常。

LazyInitializer

  • 节省空间,也可能节省时间:无需Lazy<T>为您声明的每个变量初始化一个新对象。

  • 允许您延迟提供初始化参数,直到使用时间LazyInitializer.EnsureInitialized(ref x, () => new X(initParameters))

总之,您只需要LazyInitializer空间(可能还有时间)有限的情况下使用,或者如果您无法在声明时指定所有初始化参数。

我个人更喜欢Lazy<T>尽可能,因为我发现它提供了更清晰的代码,而且我不必自己明确地处理初始化异常。

于 2016-02-03T09:32:15.670 回答
2

LazyInitializer允许您使用延迟初始化功能,而无需为每个延迟初始化的对象创建一个类。

以下LazyInitializer是可以提供的好处。

Lazy<T>对于这种情况,使用产生的开销是否过多,取决于您自己的要求。

于 2012-08-17T14:20:13.897 回答
1

Lazy Initializing 的文档解释得很清楚。请参阅延迟初始化。简而言之,为您使用的每个实例Lazy<T>创建一个新类(构造的泛型)T,并为您贴标的每个T实例创建该类的新实例——即使底层T从未初始化。使用 的静态方法LazyIntializer进行编码可能会更复杂,但可以避免Lazy<T>包装器实例的开销。

于 2012-08-17T14:21:28.823 回答
1

我认为这回答了您的问题:LazyInitialization System.Threading.ThreadLocal 的另一种方式

它与 Lazy 相同,但唯一的区别是它以 Thread Local 为基础存储数据。因此,每个 Thread 上的值将是 Initialized object 的不同副本

更多细节来自:http ://www.cshandler.com/2011/09/different-ways-of-lazy-initialization.html

于 2013-06-04T08:13:54.190 回答
-1
`LazyInitializer`  of an object means its object creation is deferred until it is ued first.

创建这种形式的对象是为了提高性能并减少内存浪费。

而要定义一个惰性初始化类型,我们使用Lazy<T>(通用形式)LazyInitializer

E.g:
 Lazy<Orders> _orders = new Lazy<Orders>();

如需进一步参考:

http://msdn.microsoft.com/en-us/library/dd997286.aspx

于 2012-08-17T14:12:30.467 回答