我发现有关Lazy的文章:C#4.0中的惰性-懒惰

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

评论

替换为:get {if(foo == null)foo = new Foo();返回foo; }。而且有成千上万的可能使用它...

请注意,get {if(foo == null)foo = new Foo();返回foo; }不是线程安全的,而Lazy 默认情况下是线程安全的。

来自MSDN:重要信息:惰性初始化是线程安全的,但创建后不会保护对象。除非类型是线程安全的,否则您必须在访问对象之前将其锁定。

#1 楼

通常,当您想在第一次实际使用某个实例时实例化它。这将创建成本的时间推迟到需要时/而不是总产生成本。

通常,当可能使用或可能不使用该对象并且构造它的成本不是琐碎。

评论


为什么不总是使用Lazy?

–TruthOf42
2013年6月11日13:33

这样做会招致初次使用的成本,并且可能会花费一些锁定开销(或者如果没有则牺牲线程安全性)。因此,应该仔细选择它,除非需要,否则不要使用它。

–詹姆斯·迈克尔·哈尔(James Michael Hare)
2013年6月11日20:23在

詹姆斯,请您谈谈“而且建造成本是微不足道的”吗?就我而言,我的课堂上有19个属性,在大多数情况下,只需要查看2或3个属性。因此,我正在考虑使用Lazy 实现每个属性。但是,要创建每个属性,我需要进行线性内插(或双线性内插),这很简单,但确实需要一定的成本。 (您是否建议我去做自己的实验?)

–本
2014年1月31日下午16:59

詹姆斯,根据我的建议,我做了自己的实验。看我的帖子。

–本
2014年1月31日22:20

您可能希望初始化/实例化系统“启动”期间的所有内容,以防止高吞吐量,低延迟的系统中的用户延迟。这只是不“总是”使用Lazy的众多原因之一。

–德里克
2014-09-30 12:34



#2 楼

您应该尝试避免使用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.
    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;
}


评论


我讨厌阅读您在使用单身人士:D ...时应避免使用Singletons ...现在,我需要了解为什么我应该避免使用单身人士:D

–巴特杯
13年8月29日在5:58

当Microsoft在示例中停止使用Singletons时,我将停止使用它们。

– eaglei22
17年5月2日在17:16

我倾向于不同意需要避免使用Singletons的观点。当遵循依赖注入范例时,无论哪种方式都没有关系。理想情况下,所有依赖项仅应创建一次。在高负载情况下,这可以降低GC的压力。因此,从类本身内部使它们成为Singleton很好。大多数(如果不是全部)现代DI容器都可以按照您选择的任何一种方式来处理它。

–李·格里索姆(Lee Grissom)
17年6月29日在20:17

您不必使用那样的单例模式,而可以使用任何di容器为单例配置您的类。容器将为您处理开销。

– VivekDev
17年11月8日在12:22

一切都有目的,在某些情况下单例是一种很好的方法,而在某些情况下则不是:)。

–霍克西
20 Mar 4 '20 at 14:39

#3 楼

懒惰加载很方便的一个现实世界的好例子是ORM(对象关系映射器),例如Entity Framework和NHibernate。

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

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

这是延迟加载的理想场所,因为如果Order属性是延迟的,它将不会除非您确实需要,否则请获取所有客户的订单。您可以枚举在Order属性耐心地睡眠时,Customer对象仅获得其名称和电话号码,以便在需要时准备就绪。

评论


不好的例子,因为这种延迟加载通常已经内置在ORM中。您不应该开始将Lazy 值添加到POCO中以进行延迟加载,而应使用特定于ORM的方式来做到这一点。

–迪纳隆
13年5月23日在5:42

@Dyna此示例是指ORM的内置延迟加载,因为我认为这以一种清晰,简单的方式例证了延迟加载的有用性。

– Despertar
13年5月23日在6:49



所以,如果您使用的是实体框架,应该强制自己懒惰吗?还是EF为您做到了?

– Zapnologica
2014年1月14日上午9:17

@Zapnologica EF默认情况下会为您完成所有这些操作。实际上,如果您希望进行快速加载(与延迟加载相反),则必须使用Db.Customers.Include(“ Orders”)明确告知EF。这将导致订单联接在那一刻执行,而不是在首次使用Customer.Orders属性时执行。也可以通过DbContext禁用延迟加载。

– Despertar
2014年1月15日,0:18



实际上,这是一个很好的例子,因为使用Dapper等工具时可能要添加此功能。

–tbone
15年8月26日在21:58

#4 楼

我一直在考虑使用Lazy<T>属性来帮助改善我自己的代码的性能(并进一步了解它)。我来这里寻找有关何时使用它的答案,但似乎到处都是类似的短语:资源密集型对象或资源密集型任务的执行,尤其是在程序生命周期中可能不会创建或执行此类任务的情况下。


来自MSDN Lazy

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

最后我决定尝试自己的测试,我想我会在这里分享结果。不幸的是,我并不是真正从事此类测试的专家,因此很高兴收到可以提出改进建议的评论。

说明

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

我创建了一个单独的测试每个方法具有20个测试属性的测试类(将它们称为t属性)。




GetInterp类:每次获得t属性时都运行线性插值。 br />
InitInterp类:通过对构造函数中的每个插值运行线性插值来初始化t属性。 get只是返回一个double。

InitLazy类:将t属性设置为Lazy属性,以便在首次获得该属性时运行一次线性插值。后续获取应该只返回已经计算出的double。

测试结果以毫秒为单位,是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结果:第一个Get(平均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结果:第二个Get(平均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属性),但是实例化(测试1)仅花费0.09毫秒,而GetInterp只需0.28毫秒即可完成一次第一次进行线性插值(测试2),第二次进行线性插值(测试3)。

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

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

尽管某些结果符合预期,但由于代码优化,还有一些非常有趣的意外结果。即使对于看起来像在构造函数中完成大量工作的类,实例化结果也表明,与获得double属性相比,它们的创建速度仍然非常快。尽管该领域的专家可以发表评论并进行更彻底的调查,但我个人的感觉是,我需要对生产代码再次进行此测试,以便检查那里可能还会进行哪种优化。但是,我希望InitInterp成为您的理想之路。

评论


也许您应该发布测试代码以重现输出,因为如果不知道您的代码,将很难提出任何建议

– WiiMaxx
2014年11月27日上午11:04

我认为主要的折衷是在内存使用(惰性)和cpu使用(非惰性)之间。由于懒惰必须做一些额外的记账工作,因此InitLazy会比其他解决方案使用更多的内存。在检查它是否已经有值的同时,它对每次访问的性能可能也会有轻微的影响。巧妙的技巧可以消除这些开销,但是需要IL中的特殊支持。 (Haskell通过使每个惰性值成为函数调用来执行此操作;一旦生成该值,它将替换为每次都会返回该值的函数。)

– jpaugh
17年8月28日在16:03



#5 楼

仅举马修(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 ;
}


评论


我总是为此使用IoC容器。

– Jowen
15年2月2日,13:57

我强烈同意考虑为此使用IoC容器。但是,如果您想要一个简单的延迟初始化对象单例,则还应考虑一下,如果您不需要这样做是线程安全的,则使用If进行手动操作可能是最好的选择,因为它考虑了延迟处理自身的性能开销。

– Thulani Chivandikwa
15年7月2日在6:28

#6 楼

来自MSDN:


使用Lazy实例延迟大型或资源密集型对象的创建或资源密集型任务的执行,尤其是当此类创建或执行可能不



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

#7 楼

您应该看一下此示例以了解延迟加载体系结构

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);”

输出->未创建值