C# 实施";“懒散的财产”;课堂-这是个好主意吗?

C# 实施";“懒散的财产”;课堂-这是个好主意吗?,c#,.net,lazy-evaluation,C#,.net,Lazy Evaluation,我经常发现自己写的一个属性被懒散地评估。比如: if (backingField == null) backingField = SomeOperation(); return backingField; 它不是很多代码,但是如果你有很多属性,它会重复很多次 我正在考虑定义一个名为LazyProperty的类: public class LazyProperty<T> { private readonly Func<T> getter;

我经常发现自己写的一个属性被懒散地评估。比如:

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;
    }
}
这将被公司的大多数人使用,因为它将进入我们大多数产品共享的公共类库

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

  • 更少的代码
  • 漂亮代码
不利的一面是,查看代码并准确确定发生了什么会更加困难,特别是如果开发人员不熟悉LazyProperty类

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


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

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


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

我更喜欢第一个代码,因为a)这是一个具有属性的常见模式,我立即就能理解它,b)你提到的一点是:没有隐藏的魔力,你必须去寻找,以了解在哪里和何时获得价值。

我不认为担心开发人员不理解是反对这样做的一个好理由

如果你这样想,那么你就什么都做不了,因为害怕有人不理解你所做的

你可以在一个中央存储库中编写一个教程或其他东西,我们这里有一个wiki,用于编写此类注释


总的来说,我认为这是一个很好的实现方案(不想就懒散加载是否是一个好主意展开争论)

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

如果您进行任何序列化,您的方法将来可能会成为一个问题。另外,最初理解自定义类型所做的工作更容易混淆


总的来说,我赞赏您的尝试并欣赏它的聪明之处,但出于上述原因,我建议您回到原来的解决方案。

您肯定至少希望
LazyProperty
成为一种值类型,否则您会为系统中的每个“延迟加载”属性增加内存和GC压力


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

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

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

first = new LazyProperty<HeavyObject>(() => new HeavyObject { MyProperty = Value });
public Type Value
{
    get
    {
        if(ViewState["key"] == null)
            ViewState["key"] = someDefaultValue;
        return (Type)ViewState["key"];
    }
    set{ ViewState["key"] = value; }
}

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

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


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

只是过于迂腐:

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

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

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

不要这样做。

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

也就是说,默认情况下,您应该进行快速初始化,当探查器说这是您的瓶颈时,将其更改为延迟初始化


如果你想创造这种抽象,那是一种气味。

我认为这是一个有趣的想法。首先,我建议您对调用代码隐藏Lazy属性,因为您不希望将它泄漏到域模型中。你用隐式操作符做的,所以保持它

例如,我喜欢使用这种方法来处理和抽象锁定的细节。如果你这样做,那么我认为这是有价值和优点的。如果你真的为双锁模式添加了锁,那么很容易就可以实现
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;
  }
}