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

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


当前回答

从MSDN:

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

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

其他回答

再扩展一下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 ;
}

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

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

在现实世界中,延迟加载派上用场的一个很好的例子是ORM(对象关系映射器),如实体框架和NHibernate。

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

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

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

从MSDN:

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

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

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

使用延迟初始化来延迟创建大型或 资源密集型对象,或者执行资源密集型 任务,特别是当这样的创建或执行可能不会发生时 在程序的生命周期内。

从MSDN Lazy<T>类

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

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

描述

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

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

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

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

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

第1类、第2类、第3类、第4类、第5类 ------------------------------------------------------------------------ 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 0.058436 0.05891 0.068046 0.068108 0.060648 0.0628296 69.59

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

第1类、第2类、第3类、第4类、第5类 ------------------------------------------------------------------------ GetInterp 0.263 0.268725 0.31373 0.263745 0.279675 0.277775 54.38 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个属性获得)

第1类、第2类、第3类、第4类、第5类 ------------------------------------------------------------------------ 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 0.19603 0.105715 0.107975 0.10034 0.098935 0.121799 100.00

观察

GetInterp is fastest to instantiate as expected because its not doing anything. InitLazy is faster to instantiate than InitInterp suggesting that the overhead in setting up lazy properties is faster than my linear interpolation calculation. However, I am a bit confused here because InitInterp should be doing 20 linear interpolations (to set up it's t-properties) but it is only taking 0.09 ms to instantiate (test 1), compared to GetInterp which takes 0.28 ms to do just one linear interpolation the first time (test 2), and 0.1 ms to do it the second time (test 3).

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

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

结论

While some results are as expected, there are also some very interesting unexpected results probably due to code optimisations. Even for classes that look like they are doing a lot of work in the constructor, the instantiation results show that they may still be very quick to create, compared to getting a double property. While experts in this field may be able to comment and investigate more thoroughly, my personal feeling is that I need to do this test again but on my production code in order to examine what sort of optimisations may be taking place there too. However, I am expecting that InitInterp may be the way to go.