Lazy
的文章:C#4.0中的惰性-懒惰使用懒惰对象具有最佳性能的最佳实践是什么?
有人可以指出我在实际应用中的实际用途吗? ?换句话说,什么时候应该使用它?
#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
–迪纳隆
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);”
输出->未创建值
评论
替换为:get {if(foo == null)foo = new Foo();返回foo; }。而且有成千上万的可能使用它...请注意,get {if(foo == null)foo = new Foo();返回foo; }不是线程安全的,而Lazy
来自MSDN:重要信息:惰性初始化是线程安全的,但创建后不会保护对象。除非类型是线程安全的,否则您必须在访问对象之前将其锁定。