关于c#:受保护的只读字段与受保护的属性

Protected readonly field vs protected property

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

按照我将所有字段都设为私有并公开属性的习惯,我实现了以下功能:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
abstract class Foo
{
    private readonly int _field;

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

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

但后来我想知道在这里保持这个领域的隐私是否真的有很多好处。我知道财产的优势,在这个问题上,一般来说,有几个这样的问题,但它们关注的是公共领域,而不是受保护领域。

那么我是否应该切换到下面的实现呢?在这两种情况下,应注意哪些考虑因素和优缺点?

1
2
3
4
5
6
7
8
9
abstract class Foo
{
    protected readonly int _field;

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


派生类仍然是原始代码的"用户";字段也应该从中进行封装。

您应该将基类看作是安全的、可扩展的API,而不仅仅是公开其内部结构的类。保持字段私有-除了任何内容外,它允许基类更改生成该属性值的方式:)


我将离开实现或继续:

1
protected Field {get; private set;}

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

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

所以是的,保留封装。

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


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

1
2
3
4
5
6
protected Foo(int field)
{
    Field = field;
}

protected int Field { get; }


我可以考虑两个原因,一个是喜欢受保护的财产,另一个是喜欢受保护的土地。首先,考虑这个例子:

1
2
3
4
5
6
7
8
9
10
11
12
public class BaseClass
{
    protected readonly List<string> _someValues;
}

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

现在,您决定将基类更改为使用惰性实例化:

1
2
3
4
public class BaseClass
{
    protected readonly Lazy<List<string>> _someValues;
}

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

1
2
3
4
5
6
public class BaseClass
{
    private readonly Lazy<List<string>> _someValues;

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

这需要我们改变我们在心理上想象继承类的方式。我们可能开始把它想象成在一个小房子周围建一个大房子。小房子里的所有东西都在大房子里,所以它实际上都是一个大房子。没有理由把房子藏在外面。

事实上,一切都不是那样的。基类是它自己独特的实体,存在于较大的房子中。为了在多个房屋(多个继承的类)中重用它,我们需要封装它,这样继承的类就不再了解它了,就像它们所依赖的任何其他类一样。

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

这就是偏好组合而不是继承的想法。很多时候,继承被用来在类(基类和子类)之间共享功能,而这并不是它的目的。如果我们有两个不同的功能领域,我们应该用两个不同的类来实现,并且一个类可以依赖于另一个类。如果我们通过继承来实现这一点,当我们的类需要依赖另一个类时,我们将遇到障碍。我们还不能再给它一个基类。除非我们要组合不同的类来一起工作,否则我们可能会开始做一些非常糟糕的事情,比如在现有的基类中添加更多的功能或者创建更多的继承级别。很难理解,最终把我们描绘成一个角落。

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

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


使用反射可以轻松覆盖只读字段。使用属性会更难,因为字段是隐藏的(您仍然可以这样做)。所以我更喜欢一个更干净的属性,因为你可以改变吸气剂没有任何问题。

如果您考虑的是性能:属性大多数时候是内联的。

重写只读的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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);
    }
}


1
2
3
4
5
6
7
8
9
abstract class Foo
{
    protected readonly int _field;

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

将是适当的,因为您希望派生类知道它。不属于Foo类型的类将无法访问_field


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

不要忘记声明属性的C简写方法:

1
protected int Field { protected get; private set; }