受保护的只读字段与受保护的属性

时间:2012-10-23 11:17:36

标签: c# oop

我有一个抽象类,我想在其受保护的构造函数中初始化一个只读字段。我希望这个只读字段在派生类中可用。

我习惯于将所有字段设为私有并暴露属性,我按如下方式实现:

abstract class Foo
{
    private readonly int _field;

    protected Foo(int field)
    {
        _field = field;
    }

    protected int Field
    {
        get { return _field; }
    }
}

但后来我想知道在这里保持这个领域是否真的有很多好处。我知道属性的优点,一般来说这个问题有几个SO问题,但它们关注的是公共领域而不是受保护领域。

我应该切换到以下实现吗? 在任何一种情况下,需要注意哪些注意事项和优点/缺点?

abstract class Foo
{
    protected readonly int _field;

    protected Foo(int field)
    {
        _field = field;
    }
}

7 个答案:

答案 0 :(得分:11)

派生类仍然是原始代码的“用户”;字段也应该从它们封装。

您应该将基类视为安全且可扩展的API,而不仅仅是暴露其内部的类。保持字段私有 - 除了任何东西,它允许基类更改该属性的值的生成方式:)

答案 1 :(得分:5)

我将离开实施或继续:

protected Field {get; private set;}

(与父类的字段不完全相同)

属性优于字段的优点是它们更具有未来性。您可以更改父类的实现,而不会影响子级。如果没有这个属性,你就会一直致力于这个领域。 由于另一个原因,使用字段是不受欢迎的,它们会损害不透明度:类会描述字段的确切实现。

是的,请保留封装。

我唯一考虑直接使用字段以提高可读性的地方是高度耦合的类,例如状态模式中的状态,但在这种情况下,类本身就是私有的。

答案 2 :(得分:1)

Readonly属性在C#6.0中实现。 这很简单:

protected Foo(int field)
{
    Field = field;
}

protected int Field { get; }

答案 3 :(得分:0)

abstract class Foo
{
    protected readonly int _field;

    protected Foo(int field)
    {
        _field = field;
    }
}

适合您希望派生类了解它。不属于Foo类的类将无法访问_field

答案 4 :(得分:0)

使用反射可以轻松覆盖只读字段。使用属性会使其变得更难,因为该字段是隐藏的(您仍然可以执行此操作)。所以我更喜欢一个属于清洁工的房产,因为你可以毫无问题地改变吸气剂。

如果您正在考虑性能:大多数时候都会内联属性。

覆盖readonly的

class Program
{
    static void Main(string[] args)
    {
        Test t = new Test();
        t.OverrideReadonly("TestField", 5);
        t.OverrideReadonly("TestField2", 6);
        t.OverrideReadonly("TestField3", new Test());
    }
}

class Test
{
    protected readonly Int32 TestField = 1;
    protected readonly Int32 TestField2 = 2;
    protected readonly Test TestField3 = null;

    public void OverrideReadonly(String fieldName, Object value)
    {
        FieldInfo field = typeof(Test).GetField(fieldName, System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
        field.SetValue(this, value);
    }
}

答案 5 :(得分:0)

我可以想到两个理由来选择受保护的属性而不是受保护的字段。首先,考虑这个例子:

public class BaseClass 
{
    protected readonly List<string> _someValues;
}

public class InheritedClass : BaseClass
{
    public void NeedsThoseValues()
    {
         DoSomethingWith(_someValues);
    }
}

现在您决定更改基类以使用延迟实例化:

public class BaseClass 
{
    protected readonly Lazy<List<string>> _someValues;
}

现在,继承的类必须更改为调用_someValues.Value。那些继承的课程真的需要改变吗?如果field是private并且作为属性暴露给继承的类,则更改基类不会破坏继承的类:

public class BaseClass 
{
    private readonly Lazy<List<string>> _someValues;

    protected List<string> SomeValues => _someValues.Value;
}

这要求我们改变我们在心理上描绘继承类的方式。我们可能会开始想象它就像在较小的房子周围建造一个更大的房子。在较小的房子里的所有东西都在较大的房子里,所以它真的是一个大房子。没有理由将内屋从外屋隐藏起来。

实际上它并非如此。基类是它自己的独特实体,存在于较大的房屋内。为了在多个房屋(多个继承的类)中重用它,我们需要封装它,以便继承的类不仅仅需要了解它,就像它们所依赖的任何其他类一样。

这有时会产生奇怪的关系。我们试图防止继承类和基类的实现细节之间的过度耦合,但它们是耦合的,因为没有基类,继承的类不能存在。这提出了一个问题 - 为什么继承的类应该与基类有这种关系呢?为什么不将基类做什么分成它自己的类,用抽象(如接口)表示它并将其注入需要的地方?

这就是优先考虑组合而不是继承的想法。很多次继承用于在类(基类和子类)之间共享功能,而不是它的用途。如果我们有两个不同的功能区域,我们应该用两个不同的类来完成它,一个可以依赖另一个。如果我们通过继承来实现这一目标,那么当我们的课程需要依赖另一个课程时,我们会遇到麻烦。我们不能给它另一个基类。除非我们组成不同的类来协同工作,否则我们可能会开始做一些非常邪恶的事情,例如在现有基类中添加更多功能或创建更多级别的继承。它变得难以理解并最终将我们描绘成一个角落。

另一个原因是,当某人看到基类中引用的_someValues时,他们会认为它是在类中声明的字段,因为这是更常见的约定。它不会造成任何巨大的混乱,但它会花费更长的时间来弄明白。

在受保护的只读字段上优选受保护的只读属性的这些原因在许多特定情况下可能不是问题。但事先很难知道这一点,所以选择首选练习并从中解决习惯是一个好主意,同时理解你为什么要这样做。

答案 6 :(得分:-2)

使用私有字段和受保护的getter有一个小优点:您不会导致额外的间接级别。

不要忘记声明属性的c#简写方式:

protected int Field { protected get; private set; }