7

我经常发现自己编写了一个懒惰地评估的属性。就像是:

if (backingField == null) 
  backingField = SomeOperation();
return backingField;

它的代码不多,但如果你有很多属性,它确实会重复很多。

我正在考虑定义一个名为 LazyProperty 的类:

public class LazyProperty<T>
    {
    private readonly Func<T> getter;

    public LazyProperty(Func<T> getter)
    {
        this.getter = getter;
    }

    private bool loaded = false;
    private T propertyValue;

    public T Value
    {
        get
        {
            if (!loaded)
            {
                propertyValue = getter();
                loaded = true;
            }
            return propertyValue;
        }
    }

    public static implicit operator T(LazyProperty<T> rhs)
    {
        return rhs.Value;
    }
}

这将使我能够像这样初始化一个字段:

first = new LazyProperty<HeavyObject>(() => new HeavyObject { MyProperty = Value });

然后属性的主体可以简化为:

public HeavyObject First { get { return first; } }

这将被公司的大多数人使用,因为它将进入我们大多数产品共享的公共类库中。

我无法决定这是否是一个好主意。我认为这些解决方案有一些优点,例如:

  • 更少的代码
  • 更漂亮的代码

不利的一面是,查看代码并准确确定发生了什么会更加困难——尤其是在开发人员不熟悉 LazyProperty 类的情况下。

你怎么看 ?这是一个好主意还是我应该放弃它?此外,隐式运算符是一个好主意,还是如果您应该使用此类,您是否更愿意显式使用 Value 属性?

欢迎提出意见和建议:-)

4

10 回答 10

7

只是过于迂腐:

您建议的避免重复代码的解决方案:

private LazyProperty<HeavyObject> first = 
  new LazyProperty<HeavyObject>(() => new HeavyObject { MyProperty = Value });
public HeavyObject First { 
  get { 
    return first; 
  } 
}

实际上是比您不想重复的代码更多的字符:

private HeavyObject first;
public HeavyObject First { 
  get {
    if (first == null) first = new HeavyObject { MyProperty = Value };
    return first;
  }
}

除此之外,我认为隐式转换使代码非常难以理解。我不会猜到一个简单地首先返回的方法,实际上最终会创建一个 HeavyObject。我至少会放弃隐式转换并从属性中返回 first.Value 。

于 2009-01-13T19:24:56.363 回答
5

根本不要这样做。

通常,在一种情况下,使用这种惰性初始化属性是一种有效的设计选择:何时SomeOperation();是一项昂贵的操作(就 I/O 而言,例如当它需要 DB 命中或计算时)并且当您确定您通常不会需要访问它。

也就是说,默认情况下您应该进行急切初始化,当分析器说这是您的瓶颈时,将其更改为延迟初始化。

如果你有创造这种抽象的冲动,那就是一种气味。

于 2009-01-13T19:27:28.293 回答
4

当然,您至少希望LazyPropery<T>成为值类型,否则您已经为系统中的每个“延迟加载”属性添加了内存和 GC 压力。

另外,多线程场景呢?考虑同时请求该属性的两个线程。如果没有锁定,您可能会创建基础属性的两个实例。为了避免在常见情况下锁定,您需要进行双重检查锁定。

于 2009-01-13T19:07:17.867 回答
2

我更喜欢第一个代码,因为a)它是一种具有属性的常见模式,我立即理解它,并且b)你提出的观点:没有隐藏的魔法,你必须去查找以了解何时何地正在获得价值。

于 2009-01-13T19:04:12.460 回答
1

我喜欢这样的想法,因为它的代码更少,更优雅,但我会非常担心这样一个事实,即很难查看它并说出正在发生的事情。我认为它的唯一方法是使用“惰性”方式设置变量的约定,并在使用它的任何地方进行评论。现在不会有编译器或任何可以强制执行这些规则的东西,所以仍然是 YMMV。

最后,对我来说,像这样的决定归结为谁将看它以及这些程序员的质量。如果你可以相信你的开发者伙伴会正确使用它并很好地评论,那么就去做吧,但如果不是,你最好以一种易于理解和遵循的方式来做。/我的 2 美分

于 2009-01-13T19:03:09.747 回答
1

我不认为担心开发人员不理解是反对做这样的事情的一个很好的论据......

如果你这么想,那么你什么也做不了,因为害怕别人不理解你做了什么

您可以在中央存储库中编写教程或其他内容,我们这里有一个用于此类笔记的 wiki

总的来说,我认为这是一个很好的实现想法(不想开始争论延迟加载是否是一个好主意)

于 2009-01-13T19:06:43.267 回答
1

在这种情况下,我要做的是创建一个Visual Studio 代码片段。我认为这才是你真正应该做的。

例如,当我创建 ASP.NET 控件时,我经常有很多数据存储在 ViewState 中,因此我创建了如下代码片段:

public Type Value
{
    get
    {
        if(ViewState["key"] == null)
            ViewState["key"] = someDefaultValue;
        return (Type)ViewState["key"];
    }
    set{ ViewState["key"] = value; }
}

这样,只需少量工作(定义类型、键、名称和默认值)即可轻松创建代码。它是可重用的,但您没有其他开发人员可能无法理解的复杂代码的缺点。

于 2009-01-13T19:22:42.563 回答
0

我喜欢您的解决方案,因为它非常聪明,但我认为您使用它并不会赢得太多。在公共属性中延迟加载私有字段绝对是可以复制代码的地方。然而,这总是让我觉得这是一种使用模式,而不是需要重构到一个通用位置的代码。

如果您进行任何序列化,您的方法将来可能会成为一个问题。此外,最初了解您使用自定义类型所做的事情也更加令人困惑。

总的来说,我赞赏你的尝试并欣赏它的聪明,但出于上述原因,我建议你恢复原来的解决方案。

于 2009-01-13T19:06:48.430 回答
0

就个人而言,我不认为 LazyProperty 类本身提供了足够的价值来证明使用它是合理的,特别是考虑到将它用于值类型的缺点(如 Kent 所述)。如果您需要其他功能(例如使其成为多线程),则可以将其视为 ThreadSafeLazyProperty 类。

关于隐式属性,我更喜欢“值”属性。打字要多一点,但对我来说更清楚。

于 2009-01-13T19:23:57.537 回答
0

我认为这是一个有趣的想法。首先,我建议您从调用代码中隐藏惰性属性,您不想泄漏到您的域模型中,因为它是惰性的。你用隐式运算符做什么,所以保留它。

例如,我喜欢你如何使用这种方法来处理和抽象出锁定的细节。如果你这样做,那么我认为有价值和优点。如果您确实添加了锁定,请注意双重锁定模式,很容易出错。

于 2009-01-13T19:43:18.043 回答