缓存属性 vs Lazy<T>

55
在 .NET 4 中,下面这段使用了缓存属性的代码块也可以使用 System.Lazy<T> 类来编写。我测量了两种方法的性能,它们几乎是相同的。是否有任何实际的好处或者为什么我应该使用其中一种而不是另一种的魔力呢?
public static class Brushes
{
    private static LinearGradientBrush _myBrush;

    public static LinearGradientBrush MyBrush
    {
        get
        {
            if (_myBrush == null)
            {
                var linearGradientBrush = new LinearGradientBrush { ...};
                linearGradientBrush.GradientStops.Add( ... );
                linearGradientBrush.GradientStops.Add( ... );

                _myBrush = linearGradientBrush;
            }

            return _myBrush;
        }
    }
}

懒惰的<T>

public static class Brushes
{
    private static readonly Lazy<LinearGradientBrush> _myBrush =
        new Lazy<LinearGradientBrush>(() =>
            {
                var linearGradientBrush = new LinearGradientBrush { ...};
                linearGradientBrush.GradientStops.Add( ... );
                linearGradientBrush.GradientStops.Add( ... );

                return linearGradientBrush;
            }
        );

    public static LinearGradientBrush MyBrush
    {
        get { return _myBrush.Value; }
    }
}

13
使用 Lazy<T> 可以懒惰地不编写自己的实现。(当然,这是一种好方式。) - BoltClock
有趣的是,我倾向于认为这是更少的代码和更易读,但你的例子证明了这并不完全正确。但话说回来,我已经有一个支持此类操作和更常见的后备字段行为的Property<T>类。 - Steven Jeuris
Lazy<T> 允许线程安全。 - Mike Tsayper
7个回答

76

一般情况下,我会使用 Lazy<T>:

  • 它是线程安全的(在这种情况下可能不是问题,但在其他情况下可能是问题)
  • 仅从名称上就可以明白正在发生什么
  • 它允许 null 作为有效值

请注意,您不需要使用 lambda 表达式来创建委托。例如,以下方法可能会更加简洁:

public static class Brushes
{
    private static readonly Lazy<LinearGradientBrush> _myBrush =
        new Lazy<LinearGradientBrush>(CreateMyBrush);

    private static LinearGradientBrush CreateMyBrush()
    {
        var linearGradientBrush = new LinearGradientBrush { ...};
        linearGradientBrush.GradientStops.Add( ... );
        linearGradientBrush.GradientStops.Add( ... );

        return linearGradientBrush;
    }

    public static LinearGradientBrush MyBrush
    {
        get { return _myBrush.Value; }
    }
}

如果创建过程变得很复杂,尤其是涉及到循环等操作时,这将特别方便。需要注意的是,在创建代码中,您可以为GradientStops使用集合初始化器。

当然,另一种选择是不要懒惰地处理它...除非您的类中有几个这样的属性,并且只想逐个创建相关对象,否则可以在许多情况下依赖于延迟类初始化。

正如DoubleDown的回答中所指出的那样,没有办法重置此内容以强制重新计算(除非您使Lazy<T>字段不是只读的),但我很少发现这很重要。


1
在什么情况下使用Lazy会成为一个问题? - user256034
14
当您未使用.NET 4时 :) - Jon Skeet
谢谢!你列表中的三个要点正是我正在寻找的 ;) - Martin Buberl
如果您同意@DoubleDown的答案并考虑将他们的观点添加到您的答案中,我会点赞 :) - tsemer
@tsemer:这不是我个人发现有用的东西,但我已经添加了一个小注释。 - Jon Skeet
很好,但由于这是一个差异,我希望它出现在我正在点赞的答案中。已点赞 :) - tsemer

7

使用Lazy<T>,因为它准确地表达了你正在做的事情-惰性加载。

此外,它可以保持你的属性非常干净,并且是线程安全的。


4

通常不使用lazy的唯一原因是将变量重置为null,以便下一次访问会再次加载。Lazy没有重置功能,需要从头开始重新创建lazy。


请查看此答案 https://dev59.com/DG025IYBdhLWcg3whGWx#6255398 - Olmo
好观点!在我看来,这个答案和@JonSkeet的回答结合起来就是完整的答案。 - tsemer

2
Lazy<T> 会正确处理并发场景(如果您传递了正确的 LazyThreadSafetyMode),而您的示例没有任何线程安全检查。

1

Lazy<T>更简单——它明确表达了代码的意图。
而且它也是线程安全的。

请注意,如果您实际上在多个线程上使用它,您需要将其设置为[ThreadStatic];GDI+对象不能在线程之间共享。


0

Lazy有一些同步开销来提供线程安全,而缓存属性是由CLR在任何其他代码之前初始化的,您不需要支付同步成本

从可测试性的角度来看,Lazy是经过充分测试和证明的工件。

然而,在我看来,它与其他选项相比有非常轻微的开销。


Lazy 支持 'None' 线程安全模式 - user2864740

-1

如果你的性能差不多,那么使用 Lazy<T> 而不是缓存版本的唯一原因就是你不确定用户是否真的会加载该属性。

Lazy<T> 的目的是等待用户需要资源,然后在那个时刻创建它。如果他们总是需要资源,那么使用 Lazy<T> 没有意义,除非你需要它的其他用途,比如它是线程安全的。


2
-1,OP的替代方案在某种意义上是相同的,即如果从未调用该属性,则列表永远不会被实例化。从这个意义上说,它们都是“惰性”的。 - tsemer

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接