在派生类中将属性设为只读
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
继承的工作原理,由Page
的Request.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
可能会有所帮助,它派生出子类型MutableFoo
和ImmutableFoo
。所有三个类都包括一个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,即使重新定义属性本身,这些补丁点也不会改变。
【讨论】:
以上是关于在派生类中将属性设为只读的主要内容,如果未能解决你的问题,请参考以下文章