什么时候我应该使用Lazy<T>

我找到了这篇关于Lazy: c# 4.0中的懒惰-懒惰的文章

使用Lazy对象获得最佳性能的最佳实践是什么? 谁能给我指出在实际应用中的实际用途?换句话说,我应该什么时候使用它?< / p >
162246 次浏览

当你想在第一次实际使用某个东西时实例化它时,通常会使用它。这将延迟创建它的成本,直到需要时,而不是总是产生成本。

通常,当对象可能被使用,也可能不被使用,并且构造它的成本不是微不足道时,这是可取的。

从MSDN:

使用Lazy实例可以延迟大型或资源密集型对象的创建或资源密集型任务的执行,特别是当此类创建或执行在程序的生命周期内可能不会发生时。

除了James Michael Hare的回答,Lazy还提供了线程安全的值初始化。看看LazyThreadSafetyMode枚举MSDN条目,它描述了该类的各种类型的线程安全模式。

你应该尽量避免使用单例对象,但如果你确实需要,Lazy<T>可以让实现惰性的、线程安全的单例对象变得容易:

public sealed class Singleton
{
// Because Singleton's constructor is private, we must explicitly
// give the Lazy<Singleton> a delegate for creating the Singleton.
static readonly Lazy<Singleton> instanceHolder =
new Lazy<Singleton>(() => new Singleton());


Singleton()
{
// Explicit private constructor to prevent default public constructor.
...
}


public static Singleton Instance => instanceHolder.Value;
}

一个很好的现实世界的例子说明了延迟加载的用处是ORM(对象关系映射器),比如实体框架和NHibernate。

假设您有一个实体Customer,它具有Name、PhoneNumber和Orders的属性。Name和PhoneNumber是常规字符串,而Orders是一个导航属性,返回客户曾经下过的每个订单的列表。

您可能经常想要查看所有客户的信息,并获得他们的姓名和电话号码,以便给他们打电话。这是一个非常快速和简单的任务,但是想象一下,如果每次创建一个客户,它都会自动执行一个复杂的连接来返回数千个订单。最糟糕的是,你甚至不会使用订单,所以这完全是浪费资源!

这是延迟加载的理想位置,因为如果Order属性是惰性的,它将不会获取所有客户的订单,除非您确实需要它们。您可以枚举Customer对象,只获取它们的Name和Phone Number,而Order属性则耐心地处于休眠状态,以便在需要时使用。

再扩展一下Matthew的例子:

public sealed class Singleton
{
// Because Singleton's constructor is private, we must explicitly
// give the Lazy<Singleton> a delegate for creating the Singleton.
private static readonly Lazy<Singleton> instanceHolder =
new Lazy<Singleton>(() => new Singleton());


private Singleton()
{
...
}


public static Singleton Instance
{
get { return instanceHolder.Value; }
}
}

在Lazy成为框架的一部分之前,我们会这样做:

private static object lockingObject = new object();
public static LazySample InstanceCreation()
{
if(lazilyInitObject == null)
{
lock (lockingObject)
{
if(lazilyInitObject == null)
{
lazilyInitObject = new LazySample ();
}
}
}
return lazilyInitObject ;
}

我一直在考虑使用Lazy<T>属性来帮助提高我自己代码的性能(并学习更多关于它的知识)。我来这里寻找什么时候使用它的答案,但似乎我所到之处都有这样的短语:

使用延迟初始化来延迟创建一个大的或 资源密集型对象,或者执行资源密集型 任务,特别是当这样的创建或执行可能不会发生时

.在程序的生存期内

MSDN Lazy< T>类

我有点困惑,因为我不确定底线在哪里。例如,我认为线性插值是一个相当快速的计算,但如果我不需要这样做,那么惰性初始化可以帮助我避免这样做,它值得吗?

最后,我决定试试自己的测试,我想在这里分享结果。不幸的是,我真的不是做这类测试的专家,所以我很高兴收到建议改进的评论。

描述

对于我的案例,我特别感兴趣的是Lazy Properties是否可以帮助改进我的代码中执行大量插值的部分(其中大部分未使用),因此我创建了一个测试,比较了3种方法。

我为每种方法创建了一个单独的测试类,其中包含20个测试属性(让我们称之为t-properties)。

  • GetInterp类:每次得到t-property时运行线性插值。
  • InitInterp类:通过在构造函数中对每个t-属性运行线性插值来初始化t-属性。get只返回一个double。
  • InitLazy类:将t-properties设置为Lazy属性,以便在第一次获得该属性时运行一次线性插值。后续的get应该只返回一个已经计算好的double。

测试结果以毫秒为单位,是50个实例化或20个属性get的平均值。然后每个测试运行5次。

测试1结果:实例化(平均50个实例化)

Class      1        2        3        4        5        Avg       %
------------------------------------------------------------------------
GetInterp  0.005668 0.005722 0.006704 0.006652 0.005572 0.0060636 6.72
InitInterp 0.08481  0.084908 0.099328 0.098626 0.083774 0.0902892 100.00
InitLazy   0.058436 0.05891  0.068046 0.068108 0.060648 0.0628296 69.59

测试2结果:首次获得(平均20个属性获得)

Class      1        2        3        4        5        Avg       %
------------------------------------------------------------------------
GetInterp  0.263    0.268725 0.31373  0.263745 0.279675 0.277775 54.38
InitInterp 0.16316  0.161845 0.18675  0.163535 0.173625 0.169783 33.24
InitLazy   0.46932  0.55299  0.54726  0.47878  0.505635 0.510797 100.00

测试3结果:秒获得(平均20个属性获得)

Class      1        2        3        4        5        Avg       %
------------------------------------------------------------------------
GetInterp  0.08184  0.129325 0.112035 0.097575 0.098695 0.103894 85.30
InitInterp 0.102755 0.128865 0.111335 0.10137  0.106045 0.110074 90.37
InitLazy   0.19603  0.105715 0.107975 0.10034  0.098935 0.121799 100.00

观察

GetInterp是最快的实例化,因为它没有做任何事情。InitLazy实例化比InitInterp快,这表明设置惰性属性的开销比我的线性插值计算快。然而,我在这里有点困惑,因为InitInterp应该做20个线性插值(设置它的t-properties),但它只需要0.09 ms来实例化(测试1),相比之下,GetInterp第一次只需要0.28 ms来做一个线性插值(测试2),第二次需要0.1 ms来做(测试3)。

InitLazy第一次获取属性所花费的时间几乎是GetInterp的2倍,而InitInterp是最快的,因为它在实例化过程中填充了属性。(至少这是它应该做的,但为什么它的实例化结果比单一的线性插值快得多?它到底什么时候做这些插值?)

不幸的是,在我的测试中,似乎有一些自动的代码优化正在进行。GetInterp第一次获取属性的时间应该与第二次相同,但它显示的速度要快2倍以上。看起来这种优化也影响了其他职业,因为他们在测试3中都花费了相同的时间。然而,这种优化也可能发生在我自己的生产代码中,这可能也是一个重要的考虑因素。

结论

虽然有些结果是预期的,但也有一些非常有趣的意外结果,可能是由于代码优化。即使对于那些看起来在构造函数中做了大量工作的类,实例化结果表明,与获得double属性相比,创建它们可能仍然非常快。虽然这一领域的专家可能会更彻底地评论和调查,但我个人的感觉是,我需要再做一次测试,但是在我的生产代码上,以检查那里可能也发生了什么样的优化。然而,我期待InitInterp可能是前进的道路。

您应该看看这个例子来理解惰性加载体系结构

private readonly Lazy<List<int>> list = new Lazy<List<int>>(() =>
{
List<int> configList = new List<int>(Thread.CurrentThread.ManagedThreadId);
return configList;
});
public void Execute()
{
list.Value.Add(0);
if (list.IsValueCreated)
{
list.Value.Add(1);
list.Value.Add(2);


foreach (var item in list.Value)
{
Console.WriteLine(item);
}
}
else
{
Console.WriteLine("Value not created");
}
}
< p > - - > 输出——> 0 1 2

但是如果这段代码不写“list.Value.Add(0);”

output——>未创建值