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

时间:2011-07-27 16:13:38

标签: c# .net lazy-evaluation

我发现了这篇关于LazyLaziness in C# 4.0 – Lazy

的文章

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

7 个答案:

答案 0 :(得分:206)

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

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

答案 1 :(得分:110)

你应该尽量避免使用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;
}

答案 2 :(得分:76)

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

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

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

这是延迟加载的理想场所,因为如果Order属性是懒惰的,除非您确实需要它,否则它不会获取所有客户的订单。您可以枚举Customer对象,只获取其姓名和电话号码,同时Order属性耐心地睡觉,随时为您准备好。

答案 3 :(得分:37)

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

  

使用延迟初始化来推迟创建大型或   资源密集型对象,或资源密集型的执行   任务,特别是当这种创建或执行可能不会发生时   在该计划的整个生命周期中。

来自MSDN Lazy<T> Class

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

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

<强>描述

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

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

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

测试结果以毫秒为单位测量,是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

测试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属性),但与GetInterp相比,实例化(测试1)只花费0.09 ms第一次(测试2)只进行一次线性插值需要0.28 ms,第二次进行0.1 ms(测试3次)。

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

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

<强>结论

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

答案 4 :(得分: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; }
    }
}
在懒惰诞生之前,我们会这样做:

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

答案 5 :(得分:12)

来自MSDN:

  

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

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

答案 6 :(得分:-1)

您应该查看此示例以了解延迟加载架构

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");
    }
}

-> 输出-> 0 1 2

但是如果此代码未写“ list.Value.Add(0);”

输出->未创建值