【问题标题】:Cached property vs Lazy<T>缓存属性 vs Lazy<T>
【发布时间】:2011-07-05 07:43:36
【问题描述】:

在 .NET 4 中,也可以使用 System.Lazy&lt;T&gt; 类编写以下带有缓存属性的 sn-p。我测量了这两种方法的性能,结果几乎相同。为什么我应该使用一个而不是另一个有什么真正的好处或魔法?

缓存属性

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;
        }
    }
}

懒惰

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; }
    }
}

【问题讨论】:

  • 通过使用Lazy&lt;T&gt;,您将成为Lazy 来编写自己的实现。 (当然,这是一种很好的方式。)
  • 有趣,我倾向于说它的代码更少且更具可读性,但您的示例表明情况并非如此。但话又说回来,我已经有一个 Property&lt;T&gt; 类用于支持这种和更常见的支持字段行为的支持字段。
  • Lazy 允许线程安全

标签: c# .net .net-4.0 lazy-loading


【解决方案1】:

我一般会使用Lazy&lt;T&gt;

  • 它是线程安全的(在这种情况下可能不是问题,但在其他情况下会是问题)
  • 仅通过名称就可以清楚地知道发生了什么
  • 它允许 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&lt;T&gt; 字段设为非只读) - 但我很少发现这很重要。

【讨论】:

  • 在什么情况下使用 Lazy 会出现问题?
  • @user256034:当您不使用 .NET 4 时:)
  • 谢谢!您列表中的 3 点正是我要寻找的 ;)
  • 很好的积分列表,如果您同意@DoubleDown 的回答并考虑将他们的观点添加到您的回答中,我会投票赞成:)
  • @tsemer:这不是我个人认为有用的东西,但我添加了一个小注释。
【解决方案2】:

使用Lazy&lt;T&gt;,因为它准确地表达了您在做什么 - 延迟加载。

此外,它使您的财产非常干净并且是线程安全的。

【讨论】:

    【解决方案3】:

    通常不使用惰性的唯一原因是将变量重置为空,以便下一次访问导致它再次加载。懒惰没有重置,您需要从头开始重新创建懒惰。

    【讨论】:

    【解决方案4】:

    Lazy&lt;T&gt; 将正确处理并发场景(如果您传入正确的 LazyThreadSafetyMode ),而您的示例没有任何线程安全检查。

    【讨论】:

      【解决方案5】:

      Lazy&lt;T&gt; 更简单——它清楚地表达了代码的意图。
      它也是线程安全的。

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

      【讨论】:

        【解决方案6】:

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

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

        但是,在我看来,与其他选项相比,它的开销很小

        【讨论】:

        【解决方案7】:

        好吧,如果您的性能大致相同,那么在缓存版本上使用 Lazy&lt;T&gt; 的唯一原因是您不确定用户是否真的要加载该属性。

        Lazy&lt;T&gt; 的要点是等到用户需要资源,然后及时在那个实例上创建它。如果他们总是需要资源,那么使用Lazy&lt;T&gt; 是没有意义的,除非您需要它的某些其他目的,例如线程安全。

        【讨论】:

        • -1,OP 的替代方案在某种意义上是相同的,即如果从不调用该属性,则永远不会实例化该列表。从这个意义上说,他们都是“懒惰的”。
        猜你喜欢
        • 1970-01-01
        • 2016-04-21
        • 2011-03-27
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-06-20
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多