什么时候应该使用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
是最快实例化,因为它没有做任何事情。 InitLazy
比InitInterp
更快实例化,这表明设置延迟属性的开销比我的线性插值计算快。 然而,我有点困惑,因为InitInterp
应该做20个线性插值(设置它的t属性),但实例化(testing1)只需要0.09 ms,而GetInterp
只需要0.28 ms来做一个第一次是线性插值(testing2),第二次是0.1毫秒(testing3)。
它需要InitLazy
比GetInterp
两倍,第一次得到一个属性,而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条目,描述该类的各种线程安全模式。