[英]LazyInitializer vs Lazy<T> class. When to use each one
我不确定你是否还在研究这个,但我最近不得不深入研究Lazy<T>
和LazyInitializer.EnsureInitialized<T>()
的细节,所以我想我应该分享我的发现。
首先,一些数字。 我使用这两种方法对一千万个值的批处理运行了两种方法的基准测试,使用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));
友好得多
此外,如果您正在处理值类型或可能合法为null
的引用类型,则Lazy<T>
会使事情变得更简单。 使用LazyInitializer
,您必须使用第二个布尔字段来跟踪值是否已初始化,从而加剧了代码清晰度问题。 如果您想要更严格的线程安全性, Lazy<T>
也更易于使用。
总体而言,对于许多应用程序来说,大部分开销可能可以忽略不计(尽管并非总是如此——我开始研究这个的原因是因为我正在处理一个涉及数百万个非常小的延迟加载值的应用程序,而Lazy<T>
的每个实例 32 字节的开销实际上开始变得不方便)。
最后,除非您的应用程序非常占用内存,否则我认为这通常是个人喜好的问题。 对于非空引用类型,我个人认为LazyInitializer.EnsureInitialized<T>()
是一种更优雅的方法,但我也可以挖掘代码清晰度的论点。
Lazy<T>
( MSDN ) 是一个通用包装器,它允许通过持有T
工厂方法 ( Func<T>
) 并在访问Value
属性 getter 时调用它来按需创建T
的实例。
LazyInitializer
- 具有一组静态方法的静态类,这只是一个使用Activator.CreateInstance() (反射)能够实例化给定类型实例的助手。 它不保留任何本地私有字段,也不公开任何属性,因此没有内存使用开销。
值得注意的是,两个类都使用Func<T>
作为实例工厂。
MSDN用几句话介绍了LazyInitializer
类:
这些例程避免需要分配专用的延迟初始化实例,而是使用引用来确保目标在访问时已被初始化。
PS:我发现了一种有趣的方式LazyIntiializer
如何检查实例是否已经初始化,它只是将传入的引用与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
正如其他答案所说,
Lazy<T>
通常提供更清晰的代码:只需使用x = new Lazy<T>(_ => new ...)
初始化,并在您访问它的任何地方使用x.Value
。
如果多个线程同时访问未初始化的Lazy<T>
对象的Value
属性,则允许使用不同的预定义选项来处理初始化和异常。
LazyInitializer
节省空间,也可能节省时间:无需为您声明的每个变量初始化一个新的Lazy<T>
对象。
允许您延迟提供初始化参数直到使用时间: LazyInitializer.EnsureInitialized(ref x, () => new X(initParameters))
总而言之,如果空间(可能还有时间)有限,或者您不能在声明时指定所有初始化参数,您才需要使用LazyInitializer
。
就我个人而言,我更喜欢Lazy<T>
,因为我发现它提供了更清晰的代码,而且我自己不必明确处理初始化异常。
LazyInitializer
允许您使用延迟初始化功能,而无需为每个延迟初始化的对象创建一个类。
以下是LazyInitializer
可以提供的好处。
对于这种情况,使用Lazy<T>
产生的开销是否过多取决于您自己的要求。
Lazy Initializing 的文档非常清楚地解释了它。 请参阅 延迟初始化。 简而言之, Lazy<T>
为您使用的每个T
创建一个新类(构造的泛型),并为您 decalre 的每个T
实例创建一个该类的新实例——即使底层T
从未初始化。 使用LazyIntializer
的静态方法进行编码可能会更复杂,但可以避免Lazy<T>
包装器实例的开销。
我认为这回答了您的问题:LazyInitialization System.Threading.ThreadLocal 的另一种方式
它与 Lazy 相同,但唯一的区别是它在 Thread Local 基础上存储数据。 因此,每个 Thread 上的值将是 Initialized 对象的不同副本。
更多细节来自: http : //www.cshandler.com/2011/09/different-ways-of-lazy-initialization.html
`LazyInitializer` of an object means its object creation is deferred until it is ued first.
创建这种形式的对象是为了提高性能并减少内存浪费。
而要定义延迟初始化类型,我们使用LazyInitializer
类的Lazy<T>
(通用形式)
E.g:
Lazy<Orders> _orders = new Lazy<Orders>();
进一步参考:
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.