18

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

按照我将所有字段设为私有并公开属性的习惯,我按如下方式实现:

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;
    }
}
4

7 回答 7

12

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

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

于 2012-10-23T11:21:56.327 回答
5

我会离开实施或继续:

protected Field {get; private set;}

(不完全一样 Field is not readonly 对于父类)

属性相对于字段的优势在于它们更具未来性。您可以在不影响子类的情况下更改父类的实现。如果没有该属性,您承诺的字段始终是只读的。由于另一个原因,不赞成使用字段,它们会损害不透明度:该类描述了字段的确切实现。

所以是的,离开封装。

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

于 2012-10-23T11:46:35.730 回答
1

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

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

protected int Field { get; }
于 2016-11-17T15:28:29.420 回答
1

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

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;
}

现在继承的类必须更改为 call _someValues.Value。那些继承的类真的需要改变吗?如果字段是私有的并且作为属性暴露给继承的类,则更改基类不会破坏继承的类:

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

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

这需要我们改变我们在脑海中描绘继承类的方式。我们可能会开始将其可视化,就像在小房子周围建造大房子一样。小房子里的所有东西都在大房子里,所以实际上都是一所大房子。没有理由将内屋隐藏在外屋之外。

实际上,它不是那样的。基类是它自己独特的实体,存在于更大的房子中。为了在多个房屋(多个继承的类)中重用它,我们需要封装它,以便继承的类不需要知道更多关于它的信息,就像它们依赖的任何其他类一样。

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

这就是优先组合而不是继承背后的想法。很多很多时候,继承用于在类(基类和子类)之间共享功能,而这不是它的用途。如果我们有两个不同的功能领域,我们应该用两个不同的类来完成,一个可以依赖另一个。如果我们通过继承来实现这一点,当我们的类需要依赖另一个类时,我们就会遇到障碍。我们不能再给它另一个基类。除非我们组合不同的类一起工作,否则我们可能会开始做一些非常邪恶的事情,比如在现有的基类中添加更多功能或创建更多级别的继承。它变得难以理解,最终将我们逼入绝境。

另一个原因是,当有人看到_someValues基类中的引用时,他们会假设它是在使用它的类中声明的字段,因为这是更常见的约定。这不会造成任何巨大的混乱,但他们需要更长的时间才能弄清楚。

在许多特定情况下,首选受保护的只读属性而不是受保护的只读字段的这些原因可能不是问题。但是很难预先知道这一点,所以最好选择首选的做法,并在理解你为什么这样做的同时养成习惯。

于 2018-05-17T15:31:11.143 回答
0
abstract class Foo
{
    protected readonly int _field;

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

将是合适的,因为您希望派生类知道它。非类型的类Foo将无权访问_field.

于 2012-10-23T11:22:01.080 回答
0

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

如果您正在考虑性能:属性在大多数情况下都是内联的。

覆盖只读的

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);
    }
}
于 2012-10-23T11:22:53.133 回答
-2

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

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

protected int Field { protected get; private set; }
于 2012-10-23T11:22:14.237 回答