【问题标题】:Make property readonly in derived class在派生类中将属性设为只读
【发布时间】:2011-05-23 04:20:25
【问题描述】:

我正在覆盖我的派生类中的一个属性,我想让它成为只读的。 C# 编译器不允许我更改访问修饰符,因此它必须保持公开。

最好的方法是什么?我应该在set { } 中添加一个InvalidOperationException 吗?

【问题讨论】:

  • 我认为我们需要更多背景信息。

标签: c# properties overriding access-modifiers


【解决方案1】:

让 setter 在派生类中抛出 InvalidOperationException 违反了 Liskov Subsitution Principle。本质上使 setter 的使用与基类的类型相关联,这基本上消除了多态性的价值。

您的派生类必须遵守其基类的约定。如果 setter 不适用于所有情况,则它不属于基类。

解决此问题的一种方法是稍微打破层次结构。

class C1 { 
  public virtual int ReadOnlyProperty { get; } 
}
class C2 { 
  public sealed override int ReadOnlyProperty { 
    get { return Property; }
  }
  public int Property {
    get { ... }
    set { ... }
  }
}

在这种情况下,您遇到问题的类型可以继承 C1,其余的可以切换为派生自 C2

【讨论】:

  • 所以如果基类有一个IsReadOnly 属性并抛出它自己的异常,那没关系?这就是NameObjectCollectionBase > NameValueCollection > HttpValueCollection 继承的工作原理,由PageRequest.Param 使用。我理解该原理背后的原因,但在 NameObjectCollectionBase 的情况下,它与您的说法相矛盾:If the setter is not appropriate in all circumstances then it doesn't belong on the base class. 不幸的是,我使用的 .NET 类没有实现这一点(有充分的理由)。我认为这对我来说是有道理的。
  • @Nelson 拥有基类​​有 IsReadOnly 是......充其量是一种妥协。我个人认为这是一个糟糕的模式,如果将集合正确划分为可变和只读接口/类型,BCL 会更好。
  • 好的,我会更具体的。我来自 ASP.NET 的ListView。我们称之为MyCustomList。我想要ListView 的所有功能/模板,但让MyCustomList 处理获取数据、分配DataSource/Id 并防止DataSource/Id 被更改。我认为与自己重新实现所有 ListView 功能相比,抛出异常是一个很好的折衷方案。
  • 也许我应该扩展ObjectDataSource(或类似的),但也希望有一些默认模板。
  • @Nelson,我认为走ObjectDataSource 路线听起来更合理。
【解决方案2】:

您可以隐藏原始实现并返回基本实现:

class Foo
{
    private string _someString;
    public virtual string SomeString 
    {
        get { return _someString; }
        set { _someString = value; }
    }
}

class Bar : Foo
{
    public new string SomeString 
    { 
        get { return base.SomeString; }
        private set { base.SomeString = value; }
    }
}

namespace ConsoleApplication3
{
    class Program
    {
        static void Main( string[] args )
        {
            Foo f = new Foo();
            f.SomeString = "whatever";  // works
            Bar b = new Bar();
            b.SomeString = "Whatever";  // error
        }
    }
}

然而,。正如 Jared 所暗示的,这是一种奇怪的情况。为什么不在基类中将设置器设为私有或受保护?

【讨论】:

  • 除非你从你的例子中做“ Foo f2 = b; f.SomeString = "eeba geeba!"; " ,编译器让你。
  • @mjfgates:我猜你的意思是Foo f = new Bar(); f.SomeString = "blah"?这就是我没有走这条路的原因。我无法更改基类,因为我是从标准 ASP.NET Web 控件派生的。
  • 不,我是说我写的。在这种情况下,您调用 Foo.SomeString 是因为 SomeString 在 Bar 中不是多态的,它隐藏了 Foo 实现。
  • @Ed:我指的是 mjfgates 写的东西(尽管间接地是关于你写的东西)。无论如何,我了解这是如何工作的,并且不希望两个实现因您使用的类型而不同。
  • 抱歉,我错过了手机上的回复。我同意 100% 这不是最优的。问题出在最初的请求中。如果您做出错误的设计选择,您可能会直接做出更多错误的设计选择。我根本不会尝试隐藏 setter 并避免破坏界面,
【解决方案3】:

我认为这种情况更好的解决方案是新关键字

public class Aclass {        
    public int ReadOnly { get; set; }
}

public class Bclass : Aclass {        
    public new int ReadOnly { get; private set; }
}

【讨论】:

  • 使用新关键字可以隐藏基本的 get 和 set 访问器
【解决方案4】:

不允许在 C# 中隐藏基类的公共成员,因为有人可以获取派生类的实例,将其填充到对基类的引用中,然后以这种方式访问​​属性。

如果你想让一个类提供大部分但不是全部的另一个类的接口,你将不得不使用聚合。不要从“基类”继承,只需按值在“派生”中包含一个,并为您希望公开的“基类”功能部分提供您自己的成员函数。然后,这些函数可以将调用转发到适当的“基类”函数。

【讨论】:

    【解决方案5】:

    只需使用 new 关键字定义属性,并不要在派生类中提供 Setter 方法。这也将隐藏基类属性,但是正如 mjfgates 提到的,仍然可以通过将基类属性分配给基类实例来访问基类属性。这就是多态性。

    【讨论】:

      【解决方案6】:

      在某些情况下,您描述的模式是合理的。例如,拥有一个抽象类MaybeMutableFoo 可能会有所帮助,它派生出子类型MutableFooImmutableFoo。所有三个类都包括一个IsMutable 属性和方法AsMutable()AsNewMutable()AsImmutable()

      在这种情况下,如果MaybeMutableFoo 的合同明确规定除非IsMutable 返回true,否则setter 可能无法工作,那么MaybeMutableFoo 公开读写属性是完全正确的。具有MaybeMutableFoo 类型字段且恰好包含ImmutableFoo 实例的对象可能对该实例非常满意,除非或直到它必须写入它,然后它将用通过@ 返回的对象替换该字段987654332@ 然后将其用作可变 foo (它会知道它是可变的,因为它刚刚替换了它)。让MaybeMutableFoo 包含一个 setter 将避免在该字段被引用可变实例后进行任何未来类型转换。

      允许这种模式的最佳方法是避免实现虚拟或抽象属性,而是实现非虚拟属性,其 getter 和 setter 链接到虚拟或抽象方法。如果有一个基类

      public class MaybeMutableFoo
      {
          public string Foo {get {return Foo_get();} set {Foo_set(value);}
          protected abstract string Foo_get();
          protected abstract void Foo_set(string value};
      }
      

      然后派生类ImmutableFoo 可以声明:

          new public string Foo {get {return Foo_get();}
      

      使其Foo 属性为只读,而不会影响为抽象Foo_get()Foo_set() 方法编写覆盖代码的能力。请注意,Foo 的只读替换不会改变属性 get 的行为;只读版本链接到与基类属性相同的基类方法。这样做可以确保有一个补丁点用于更改属性 getter,一个补丁点用于更改 setter,即使重新定义属性本身,这些补丁点也不会改变。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2016-02-02
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-04-12
        • 1970-01-01
        • 2019-12-13
        相关资源
        最近更新 更多