【发布时间】:2011-05-23 04:20:25
【问题描述】:
我正在覆盖我的派生类中的一个属性,我想让它成为只读的。 C# 编译器不允许我更改访问修饰符,因此它必须保持公开。
最好的方法是什么?我应该在set { } 中添加一个InvalidOperationException 吗?
【问题讨论】:
-
我认为我们需要更多背景信息。
标签: c# properties overriding access-modifiers
我正在覆盖我的派生类中的一个属性,我想让它成为只读的。 C# 编译器不允许我更改访问修饰符,因此它必须保持公开。
最好的方法是什么?我应该在set { } 中添加一个InvalidOperationException 吗?
【问题讨论】:
标签: c# properties overriding access-modifiers
让 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 类没有实现这一点(有充分的理由)。我认为这对我来说是有道理的。
IsReadOnly 是......充其量是一种妥协。我个人认为这是一个糟糕的模式,如果将集合正确划分为可变和只读接口/类型,BCL 会更好。
ListView。我们称之为MyCustomList。我想要ListView 的所有功能/模板,但让MyCustomList 处理获取数据、分配DataSource/Id 并防止DataSource/Id 被更改。我认为与自己重新实现所有 ListView 功能相比,抛出异常是一个很好的折衷方案。
ObjectDataSource(或类似的),但也希望有一些默认模板。
ObjectDataSource 路线听起来更合理。
您可以隐藏原始实现并返回基本实现:
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 f = new Bar(); f.SomeString = "blah"?这就是我没有走这条路的原因。我无法更改基类,因为我是从标准 ASP.NET Web 控件派生的。
我认为这种情况更好的解决方案是新关键字
public class Aclass {
public int ReadOnly { get; set; }
}
public class Bclass : Aclass {
public new int ReadOnly { get; private set; }
}
【讨论】:
不允许在 C# 中隐藏基类的公共成员,因为有人可以获取派生类的实例,将其填充到对基类的引用中,然后以这种方式访问属性。
如果你想让一个类提供大部分但不是全部的另一个类的接口,你将不得不使用聚合。不要从“基类”继承,只需按值在“派生”中包含一个,并为您希望公开的“基类”功能部分提供您自己的成员函数。然后,这些函数可以将调用转发到适当的“基类”函数。
【讨论】:
只需使用 new 关键字定义属性,并不要在派生类中提供 Setter 方法。这也将隐藏基类属性,但是正如 mjfgates 提到的,仍然可以通过将基类属性分配给基类实例来访问基类属性。这就是多态性。
【讨论】:
在某些情况下,您描述的模式是合理的。例如,拥有一个抽象类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,即使重新定义属性本身,这些补丁点也不会改变。
【讨论】: