当你想要实例化某个东西,且希望在第一次使用时才实例化它时,通常会使用它。这样可以推迟创建它的成本,直到需要它为止,而不总是承担成本。
通常情况下,当对象可能被使用或可能不被使用,并且构造它的成本是相当大的时,使用该方法是最好的选择。
Lazy<T>
实现每个属性。但是,创建每个属性需要进行线性插值(或双线性插值),这相当简单但确实需要一定的成本。(你会建议我自己进行实验吗?) - Ben您应该尽量避免使用单例模式,但如果确实需要使用,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;
}
Db.Customers.Include("Orders")
。这将导致在那一刻执行订单连接,而不是首次使用 Customer.Orders
属性时。也可以通过 DbContext 禁用延迟加载。 - DespertarLazy<T>
属性来帮助提高我的代码性能(并学习更多关于它的知识)。我来这里寻找有关何时使用它的答案,但似乎无论我去哪里都会出现以下短语:
来自MSDN Lazy<T> Class 我有点困惑,因为我不确定该如何划分界限。例如,我认为线性插值是一个相当快速的计算,但如果我不需要进行插值,那么延迟初始化可以帮助我避免进行插值吗?这是否值得?使用延迟初始化来推迟创建大型或资源密集型对象,或执行资源密集型任务,特别是当这种创建或执行可能不会在程序生命周期内发生时。
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
实例化速度最快,因为它没有执行任何操作。与 InitInterp
相比,InitLazy
实例化速度更快,表明设置延迟属性的开销比我的线性插值计算更快。然而,我有点困惑,因为 InitInterp
应该执行 20 次线性插值(以设置 t 属性),但仅在实例化时占用了 0.09 毫秒(测试1)。相比之下,GetInterp
首次进行一次线性插值需要 0.28 毫秒(测试2),第二次只需 0.1 毫秒(测试3)。
首次获取属性时,InitLazy
的时间几乎是 GetInterp
的两倍长,而 InitInterp
最快,因为它在实例化期间填充了其属性。(至少应该这样做,但为什么实例化结果比单个线性插值快得多?它何时进行这些插值?)
不幸的是,我的测试中存在某些自动代码优化。首次获取属性时,GetInterp
应该与第二次获取属性时所需时间相同,但它的速度显示为快了两倍以上。看起来这种优化也会影响其他类,因为它们在测试3中的速度几乎相同。然而,这样的优化也可能发生在我的生产代码中,这也可能是一个重要的考虑因素。
结论
虽然有些结果如预期,但也有一些非常有趣且出乎意料的结果,可能是由于代码优化引起的。即使对于看起来在构造函数中执行了很多工作的类,实例化结果也表明它们可能非常快速地创建,相比之下获取 double 属性则慢得多。虽然领域专家可能能够进行更深入的评论和调查,但我个人感觉需要在我的生产代码上再次进行此测试,以研究可能正在发生的优化类型。然而,我期望 InitInterp
可能是最佳选择。
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的实例推迟创建大型或资源密集型对象,或推迟执行资源密集型任务,尤其是在这样的创建或执行可能不会在程序的生命周期内发生时。
除了James Michael Hare的答案外,Lazy提供线程安全初始化您的值。请参阅LazyThreadSafetyMode枚举MSDN条目,描述此类的各种类型的线程安全模式。
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);"
输出 --> 值未创建
List<T>
是一个非常快速的操作,而且List<T>
不是线程安全的。在任何情况下,延迟初始化一个空的List<T>
都是没有意义的。 - undefined
get { if (foo == null) foo = new Foo(); return foo; }
。而且有无数个可能使用它的地方... - Kirk Wollget { if (foo == null) foo = new Foo(); return foo; }
不是线程安全的,而Lazy<T>
默认是线程安全的。 - Matthew