在派生类中将属性设为只读

Posted

技术标签:

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

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

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

【问题讨论】:

我认为我们需要更多背景信息。 【参考方案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,即使重新定义属性本身,这些补丁点也不会改变。

【讨论】:

以上是关于在派生类中将属性设为只读的主要内容,如果未能解决你的问题,请参考以下文章

QML:在派生类中使属性只读

在派生类中将公共父析构函数设为私有

在 Python 中将类实例属性设为只读

将基类转换为派生类[重复]

在 C++ 中将派生类类型的向量链接到父类类型的向量

在 C# 中将 MetadataType 添加到派生类