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

dan*_*iax 312 .net c# lazy-evaluation

我发现这篇文章是关于Lazy:C#4.0中的懒惰 - 懒惰

使用Lazy对象获得最佳性能的最佳实践是什么?有人能指出我在实际应用中的实际用途吗?换句话说,我什么时候应该使用它?

Jam*_*are 229

当您想要在第一次实际使用时实例化某些内容时,通常会使用它.这延迟了创建它的成本,直到需要/何时需要而不是总是产生成本.

通常,当对象可能使用或不使用时,这是优选的,并且构建它的成本是非平凡的.

  • 为什么不总是使用懒惰? (115认同)
  • 它会在首次使用时产生成本,并且可能会使用一些锁定开销(或者如果没有则牺牲线程安全性)来执行此操作.因此,应谨慎选择,除非需要,否则不要使用. (41认同)
  • 您可能希望在系统启动期间初始化/实例化所有内容,以防止高吞吐量,低延迟系统中的用户延迟.这只是不"总是"使用Lazy的众多原因之一. (15认同)
  • 詹姆斯,请你继续扩展"并且构建的成本是非平凡的"?在我的情况下,我的课程中有19个属性,在大多数情况下,只需要查看2或3个属性.因此我正在考虑使用`Lazy <T>`来实现每个属性.但是,为了创建每个属性,我正在进行线性插值(或双线性插值),这是相当微不足道的,但确实有一些成本.(你打算建议我去做自己的实验吗?) (3认同)
  • 詹姆斯,按照我自己的建议,我做了自己的实验.[见我的帖子](http://stackoverflow.com/a/21491131/340045). (3认同)

Mat*_*hew 119

你应该尽量避免使用Singletons,但如果你真的需要,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;
}
Run Code Online (Sandbox Code Playgroud)

  • 我讨厌阅读你应该尽量避免在我使用它们时使用Singletons:D ...现在我需要知道为什么我应该试着避免它们:D (35认同)
  • 当Microsoft停止在他们的示例中使用它们时,我将停止使用Singletons. (19认同)
  • @Bart http://stackoverflow.com/questions/137975/what-is-so-bad-about-singletons (8认同)
  • 一切都有一个目的,在某些情况下单例是一个好的方法,在某些情况下则不是:)。 (4认同)
  • 我倾向于不同意需要避免单身人士的想法.遵循依赖注入范例时,无论哪种方式都无关紧要.理想情况下,所有依赖项只应创建一次.这可以降低高负荷情况下GC的压力.因此,让它们成为班级中的单身人士就可以了.大多数(如果不是全部)现代DI容器都能以您选择的方式处理它. (3认同)
  • 您不必使用这样的单例模式,而是使用任何 di 容器将您的类配置为单例。容器将为您解决开销。 (2认同)

Des*_*tar 82

延迟加载派上用场的一个很好的现实示例是ORM(对象关系映射器),例如Entity Framework和NHibernate.

假设您有一个实体Customer,其中包含Name,PhoneNumber和Orders的属性.Name和PhoneNumber是常规字符串,但Orders是一个导航属性,它返回客户所做的每个订单的列表.

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

这是延迟加载的最佳位置,因为如果Order属性是惰性的,除非您确实需要它,否则它不会获取所有客户的订单.您可以枚举Customer对象,只获取其名称和电话号码,同时Order属性耐心地休眠,随时为您准备好.

  • @Dyna这个例子是指ORM的内置延迟加载,因为我认为这以简单明了的方式举例说明了延迟加载的有用性. (50认同)
  • 不好的例子,因为延迟加载通常已经内置到ORM中.您不应该开始向POCO添加Lazy <T>值以获得延迟加载,而是使用特定于ORM的方法来执行此操作. (33认同)
  • @Zapnologica EF默认为您完成所有这些操作.事实上,如果你想要预先加载(与延迟加载相反),你必须使用`Db.Customers.Include("Orders")`明确地告诉EF.这将导致在该时刻执行订单连接,而不是在首次使用`Customer.Orders`属性时执行.也可以通过DbContext禁用延迟加载. (7认同)
  • 实际上,这是一个很好的示例,因为在使用Dapper之类的程序时可能要添加此功能。 (2认同)

Ben*_*Ben 38

我一直在考虑使用Lazy<T>属性来帮助提高我自己的代码的性能(并学习更多关于它的信息).我来这里寻找关于何时使用它的答案,但似乎我去的每个地方都有这样的短语:

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

来自MSDN Lazy <T> Class

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

最后我决定尝试自己的测试,我想我会在这里分享结果.不幸的是,我并不是真正做这类测试的专家,因此我很乐意收到有关改进的评论.

描述

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

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

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

测试结果以ms为单位测量,是50个实例或20个属性获得的平均值.然后每次测试运行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
Run Code Online (Sandbox Code Playgroud)

测试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
Run Code Online (Sandbox Code Playgroud)

测试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
Run Code Online (Sandbox Code Playgroud)

意见

GetInterp是最快的实例化,因为它没有做任何事情.InitLazy实例化比InitInterp设置延迟属性的开销比线性插值计算更快.但是,我在这里有点困惑,因为它InitInterp应该进行20次线性插值(设置它的t-属性),但实例化(测试1)只花费0.09 ms,相比之下只GetInterp需要0.28 ms进行一次线性插值第一次(测试2),第二次测试0.1毫秒(测试3).

InitLazyGetInterp第一次获取属性要花费近2倍,而InitInterp速度最快,因为它在实例化期间填充了它的属性.(至少这应该是它应该做的但为什么它的实例化结果比单个线性插值快得多?当它正在进行这些插值时?)

不幸的是,在我的测试中看起来有一些自动代码优化.它应该花费GetInterp相同的时间第一次获得属性,因为它第二次获得属性,但它显示的速度超过2倍.看起来这种优化也会影响其他类,因为它们都需要大约相同的时间用于测试3.但是,这样的优化也可能发生在我自己的生产代码中,这也可能是一个重要的考虑因素.

结论

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

  • 也许你应该发布你的测试代码来重现你的输出,因为不知道你的代码就很难提出任何建议 (26认同)
  • 我相信主要的权衡是内存使用(惰性)和CPU 使用(非惰性)之间。因为“lazy”必须进行一些额外的簿记,所以“InitLazy”将比其他解决方案使用更多的内存。当它检查它是否已经有一个值时,它还可能对每次访问造成轻微的性能影响;聪明的技巧可以消除这种开销,但这需要 IL 的特殊支持。(Haskell 通过将每个惰性值设为函数调用来实现此目的;一旦生成该值,它就会被替换为每次都返回该值的函数。) (2认同)

Thu*_*kwa 14

只是指出Mathew发布的例子

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; }
    }
}
Run Code Online (Sandbox Code Playgroud)

在懒惰出生之前,我们会这样做:

private static object lockingObject = new object();
public static LazySample InstanceCreation()
{
    if(lazilyInitObject == null)
    {
         lock (lockingObject)
         {
              if(lazilyInitObject == null)
              {
                   lazilyInitObject = new LazySample ();
              }
         }
    }
    return lazilyInitObject ;
}
Run Code Online (Sandbox Code Playgroud)

  • 我总是使用IoC容器. (6认同)

Vas*_*sea 12

来自MSDN:

使用Lazy实例来推迟创建大型或资源密集型对象或执行资源密集型任务,尤其是在程序生命周期内可能不会发生此类创建或执行时.

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