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

我发现这篇关于Lazy : 懒惰在C#4.0 – 懒惰

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

当你想在第一次实际使用它的时候,你通常使用它。 这延迟了创build它的成本,直到需要/需要时而不是总是产生成本。

通常这是最好的,当对象可能会或可能不会被使用,并构build它的成本是不平凡的。

你应该尽量避免使用Singleton,但是如果你需要的话, Lazy<T>使得懒惰的,线程安全的singleton变得容易:

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

ORM的(对象关系映射器)如entity framework和NHibernate是一个很好的现实世界中惰性加载派生的例子。

假设您有一个具有名称,电话号码和订单属性的实体客户。 姓名和电话号码是常规string,但是订单是一个导航属性,它返回客户曾经做过的每一个订单的列表。

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

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

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

使用延迟初始化推迟创build大型资源密集型对象或执行资源密集型任务,特别是在程序生存期间不会发生这种创build或执行的情况下。

从MSDN Lazy <T>类

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

最后我决定尝试自己的testing,我想我会在这里分享结果。 不幸的是,我不是一个真正的专家做这些testing,所以我很乐意得到意见,build议改善。

描述

对于我的情况,我特别感兴趣的是看看Lazy Properties是否可以帮助改进我的代码的一部分,这些代码有很多插值(大部分是未使用的),所以我创build了一个比较3种方法的testing。

我为每个方法创build了一个包含20个testing属性的单独testing类(让我们称它们为t属性)。

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

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

testing1结果:实例化(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 

testing2结果:首先得到(平均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 

testing3结果:第二次获得(平均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是最快实例化,因为它没有做任何事情。 InitLazyInitInterp更快实例化,这表明设置延迟属性的开销比我的线性插值计算快。 然而,我有点困惑,因为InitInterp应该做20个线性插值(设置它的t属性),但实例化(testing1)只需要0.09 ms,而GetInterp只需要0.28 ms来做一个第一次是线性插值(testing2),第二次是0.1毫秒(testing3)。

它需要InitLazyGetInterp两倍,第一次得到一个属性,而InitInterp是最快的,因为它在实例化时填充了它的属性。 (至less应该这样做,但为什么它的实例化结果比单个线性插值要快得多?究竟是在做这些插值?)

不幸的是,它看起来像我的testing中有一些自动代码优化。 它应该让GetInterp在第一次获得财产的同时获得一次财产,但它显示速度提高了一倍以上。 看起来这个优化也影响了其他类,因为它们都花费大约相同的时间进行testing3.然而,这样的优化也可能发生在我自己的生产代码中,这也可能是一个重要的考虑因素。

结论

虽然一些结果如预期的那样,但也可能由于代码优化而出现一些非常有趣的意外结果。 即使对于看起来在构造函数中做了大量工作的类,实例化结果也显示出,与获取双重属性相比,它们可能仍然非常快速创build。 虽然这个领域的专家也许能够更深入地评论和调查,但我个人的感觉是,我需要再次进行这个testing,但是还要根据我的生产代码来检查那里可能会发生什么样的优化。 不过,我期待InitInterp可能会走。

只要指出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 ; } 

来自MSDN:

使用Lazy实例可推迟创build大型资源密集型对象或执行资源密集型任务,特别是在程序生命周期内不会发生这种创build或执行的情况下。

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