【问题标题】:Methods for Lazy Initialization with properties使用属性进行延迟初始化的方法
【发布时间】:2011-06-04 17:58:17
【问题描述】:

我目前正在更改一个广泛使用的类,以将尽可能多的昂贵初始化从类构造函数转移到惰性初始化属性中。下面是一个示例(在 c# 中):

之前:

public class ClassA
{
    public readonly ClassB B;

    public void ClassA()
    {
        B = new ClassB();
    }
}

之后:

public class ClassA
{
    private ClassB _b;

    public ClassB B
    {
        get
        {
            if (_b == null)
            {
                _b = new ClassB();
            }

            return _b;
        }
    }
}

在我正在更改的类中还有很多这样的属性,有些在某些情况下没有使用(因此称为 Laziness),但如果使用它们,它们很可能会被重复调用。

不幸的是,这些属性也经常在类内部使用。这意味着私有变量 (_b) 有可能被方法直接使用而无需初始化。

有没有办法在类中只提供公共属性 (B),或者甚至是具有相同初始化时需要的替代方法?

这是从程序员转发的(显然不够主观): https://softwareengineering.stackexchange.com/questions/34270/best-methods-for-lazy-initialization-with-properties

【问题讨论】:

  • @chibacity:感谢您的重新标记

标签: c# c#-3.0 recommendation-engine lazy-initialization


【解决方案1】:

好吧,我推荐的解决方案是告诉您的同事使用该属性,而不是该字段。但是你可以像这样在某种程度上证明它是白痴:

public class ClassA
{
    private Lazy<ClassB> _b = new Lazy<ClassB>(() => new ClassB());

    public ClassB B
    {
        get
        {
            return _b.Value;
        }
    }
}

现在很难搞砸了。

【讨论】:

  • 因为他的第一个例子不是线程安全的,我认为他并不关心线程安全。而且我不知道性能影响是什么,除了它们一定很小,我只会在一些荒谬的重要内部循环中担心它。
  • @Stuart:编写Lazy&lt;&gt; 类非常简单,如果您经常编写这种模式,您可能需要考虑自己动手。
  • @mquander 更进一步,您可以通过将惰性实例化推向基类并将属性推迟到基属性来完全证明这一点。这样就不需要告诉同事不要使用该字段,他别无选择,只能使用该属性,并且它具有从 ClassA 实例中删除无关字段的双重效果。
  • @BenAlabaster:当然,你可以这样做,但我的观点是,很难误用它。尝试让ClassB 离开现场的唯一方法是致电.Value,如果同事这样做(因为他不知道该物业),那么无论如何一切都很好。就我个人而言,我认为不值得为此付出努力和代码行数。
  • @Ryan:默认情况下不是线程安全的部分是:当两个线程同时“第一次”调用它并竞相初始化它时会发生什么?检查 MSDN 以获取 Hans 提到的 LazyThreadSafetyMode 枚举。它解决了这个问题。
【解决方案2】:

您可以考虑将惰性属性推送到基类中,以避免直接访问支持变量。我知道不理想。我一直认为这是 C# 中缺乏的东西,即直接支持惰性属性。

【讨论】:

  • 如果您可以赋予属性以使其懒惰以消除所有这些需求并具有隐式延迟加载属性而无需引用 .Value。
  • @BenAlabaster 当我第一次看到自动属性时,我认为它们添加了惰性功能 - 不幸的是没有。能够使用初始化函数定义自动属性会很棒。
  • @chibacity 有趣的想法,我想知道这是否可以实现...是时候开始玩了 ;)
  • @BenAlabaster 他们肯定会引入一些新的语法绒毛和编译器后端。我认为困难的部分是实现可读且易于理解的东西。您可能还必须引入线程配置,除非您默认使用线程安全。获得一些不错的清晰语法会很棘手。 public Bling B { lazyget : new Bling(); } :)
  • @chibacity 是否不能通过返回类型和 Bling 具有无参数构造函数的事实推断出 new Bling() ?因此,您只需要 public Bling B { lazyget; } 当然,如果您没有无参数构造函数,则它需要类似于您的语法。
【解决方案3】:

@chibacity 发布(并随后)删除 [然后取消删除 :P] 使用抽象基类的替代选项。虽然它在代码分发方面可能并不理想,但它确实提供了一个很好的封装,消除了许多代码混乱,从而使 ClassA 更简洁。例如,您可以考虑结合这些技术来实现这两个目标:

public class ClassB { /* Class to be lazily instantiated */ }

public abstract class BaseA
{
    private Lazy<ClassB> _b = new Lazy<ClassB>(() => new ClassB());
    public virtual ClassB B { get { return _b.Value; } }
}

public class ClassA : BaseA
{
    public override ClassB B { get { return base.B; } }
}

乍一看,这似乎更冗长,但是当您考虑到 ClassA 是您将使用的类时,这意味着您的所有引用都通过相同的属性 - 有没有多余的不必要的字段导致潜在的混乱,没有绕过属性直接引用_b,也没有必要告诉你的同事使用哪个......只有一个。

并不是说这是正确的做法,或者这是一种应该或不应该遵循的模式,我只是指出@chibacity 建议的优势,否则可能会被忽视。

如果您可以拥有隐式延迟加载属性而不必引用 B.Value... 例如:

[Lazy]
public ClassB B { get; }

或者对于没有无参数构造函数的对象

[Lazy(() => new ClassB("Hello", "World"))]
public ClassB B { get; }

或者就像@chibacity 在评论中建议的那样

public ClassB B { lazyget; }

public ClassB B { lazyget : new ClassB(); }

唉,我不认为这些是目前任何形式的可用解决方案......

【讨论】:

  • 我喜欢带有 func 和属性的语法,但不幸的是,属性参数必须是编译时常量,所以它已经过时了 - 虽然看起来不错。 :)
  • @chibacity 这是我在编写 API 时始终意识到的一件事 - 当我调用此功能或使用此功能时,我希望语法如何?通常我会在设计 API 之前设计我希望调用的样子,以便一切都如此。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-05-21
  • 1970-01-01
  • 2020-05-10
  • 1970-01-01
  • 2010-11-30
相关资源
最近更新 更多