【问题标题】:Protected readonly field vs protected property受保护的只读字段与受保护的属性
【发布时间】:2012-10-13 07:34:40
【问题描述】:

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

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

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

【问题讨论】:

  • 考虑到这一点,publicprotected 之间并没有真正的区别。两者都是在声明程序集之外可见的。所以几乎所有关于public readonly X Ypublic X Y{get;} 的论点都适用。

标签: c# oop


【解决方案1】:

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

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

【讨论】:

    【解决方案2】:
    abstract class Foo
    {
        protected readonly int _field;
    
        protected Foo(int field)
        {
            _field = field;
        }
    }
    

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

    【讨论】:

    • 派生类仍然可以是外部的。
    • “外部”是指非 Foo 类型的类。
    【解决方案3】:

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

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

    protected int Field { protected get; private set; }
    

    【讨论】:

    • @ZaidMasud 不,私有字段将是 OP 使用它的方式。我只添加了代码来显示速记方式,而不是微优化:)
    【解决方案4】:

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

    如果您正在考虑性能:属性大部分时间都是内联的。

    覆盖只读的

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

    【讨论】:

    • 我不怀疑您关于用反射覆盖只读字段的断言,但很高兴看到参考或示例。我怀疑的部分是它可以“轻松”完成。
    • @ZaidMasud 正如我所说,这就是更好地使用私有设置器的属性的原因。那时更改字段不再那么容易了,当您想更改 getter 中的某些内容时它具有一些优势(即,如果您将支持变量替换为检索远程实例的代码)。
    • 我唯一的观点是,使用反射,私有只读字段似乎与受保护的只读字段一样容易更改。
    • @ZaidMasud 没错,但有一点不同。作为继承者,您不知道字段MyPrivateField 的名称在软件的下一个版本中是否没有更改,因此通过反射设置字段容易出错。如果您使用受保护的只读字段,作为软件供应商,您应该保留原始字段名称,因为其他任何事情都会迫使您的软件的所有用户重新编译甚至更改使用它的代码。无论如何,没有办法阻止访问,但至少你可以让它尽可能难,并使用一个属性,它也允许你进行一些代码更改。
    【解决方案5】:

    我会离开实施或继续:

    protected Field {get; private set;}
    

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

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

    所以是的,离开封装。

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

    【讨论】:

      【解决方案6】:

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

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

      【讨论】:

      • 只能在构造函数中设置和修改,与只读字段相同。它与私有集的正确性不同
      【解决方案7】:

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

      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。那些继承的类真的需要改变吗?如果字段是私有的并且作为属性暴露给继承的类,则更改基类不会破坏继承的类:

      public class BaseClass 
      {
          private readonly Lazy<List<string>> _someValues;
      
          protected List<string> SomeValues => _someValues.Value;
      }
      

      这要求我们改变对继承类的想象方式。我们可能会开始将其可视化,就像在小房子周围建造大房子一样。小房子里的所有东西都在大房子里,所以实际上都是一所大房子。没有理由向外面的房子隐藏里面的房子。

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

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

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

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

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2020-03-31
        • 2011-03-11
        • 2016-02-19
        • 2011-06-06
        • 1970-01-01
        • 1970-01-01
        • 2023-03-03
        • 1970-01-01
        相关资源
        最近更新 更多