在构造函数中重写属性或计算属性

本文关键字:属性 计算 重写 构造函数 | 更新日期: 2023-09-27 18:06:54

例如,我有一个基类,我需要一个属性将在派生类中计算。我有两个变体(SomeProperty1SomeProperty2):

public class BaseClass
{
    public int SomeProperty1{get;set;}
    public override int SomeProperty2{get;set;}
}
public class DerivedClass : BaseClass
{
    public DerivedClass()
    {
       SomeProperty1 = 100;
    }
    public override int SomeProperty2
    {
        get
        {
            return 100;
        }
    }
}

问题是SomeProperty1SomeProperty2哪个是最好的方法?

在构造函数中重写属性或计算属性

在基类中添加一个受保护的抽象方法CalcSomeProperty()

然后根据CalcSomeProperty()实现您的属性。这将强制派生类实现它。

例如:

public abstract class BaseClass
{
    public int SomeProperty
    {
        get
        {
            return CalcSomeProperty();
        }
    }
    protected abstract int CalcSomeProperty();
}

或者,您可以将属性本身设置为抽象的:

public abstract class BaseClass
{
    public abstract int SomeProperty { get; }
}

在这两种情况下,您都是在强制派生类实现属性计算。

将计算分离到一个受保护的方法中(而不是使用更简单的抽象属性)的一个优点是,如果计算很慢,您可以在具体属性实现中执行缓存:

public abstract class BaseClass
{
    protected BaseClass()
    {
        _someProperty = new Lazy<int>(CalcSomeProperty);
    }
    public int SomeProperty
    {
        get
        {
            return _someProperty.Value;
        }
    }
    protected abstract int CalcSomeProperty();
    private readonly Lazy<int> _someProperty;
}

选项1主要用于用例,当属性应该初始化一次时,初始化很容易且微不足道。

选项2允许控制初始化流程(例如,延迟初始化)。而初始化流则取决于属性的性质。

如果你真的想让在子类中覆盖,那么也许你想要的属性是virtual:

public virtual int SomeProperty2{get;set;}

尽管我最好在基类中声明一个公共属性和一个可以在子类中覆盖的受保护的虚拟属性:

// base
protected virtual int SomePropertyInternal2
{
    get
    {
        return 10;
    }
}
public int SomeProperty2
{
    get
    {
        return SomePropertyInternal2;
    }
// child
protected override int SomePropertyInternal2
{
    return 100;
}

在这种情况下,您将重写内部实现,而公共契约保持不变。

这很大程度上取决于计算的类型。如果它需要很长时间,并且在对象的生命周期内没有更改,那么将它添加到属性中会浪费计算时间。在这种情况下,我肯定会保持你的代码整洁,并在你的构造函数中初始化属性。

如果它是一个常量,那么将它保存在构造函数中会更清楚。构造函数反映了对象的样子。

显然,如果计算是动态的,则需要在属性中包含该部分。

注意:如果你有一个虚拟属性,并且你在构造函数中初始化它,那么如果你的类不是sealed,你将引入一个警告。这背后的"危险"在这篇文章中解释得比我更好。