我正在覆盖继承类中的一个属性,我希望将其设置为只读。C#编译器不允许我更改访问修饰符,因此它必须保持public。
那么,最好的方法是什么?我应该在set { }
中抛出InvalidOperationException
吗?
我正在覆盖继承类中的一个属性,我希望将其设置为只读。C#编译器不允许我更改访问修饰符,因此它必须保持public。
那么,最好的方法是什么?我应该在set { }
中抛出InvalidOperationException
吗?
在派生类中使setter抛出InvalidOperationException违反了里氏替换原则,基本上使得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
的情况下,它与您的声明相矛盾:如果setter在所有情况下都不合适,则不应该出现在基类中。
不幸的是,我正在使用一个没有实现这个功能的.NET类(有充分的理由)。我认为在我的情况下这是有意义的。 - Nelson RothermelIsReadOnly
最多只是一种妥协。个人认为这是一个可怕的模式,如果BCL将集合适当地分为可变和只读接口/类型,那么会更好。 - JaredParListView
中派生出一个类,我们称之为MyCustomList
。我想要ListView
的所有功能/模板,但是让MyCustomList
处理获取数据、分配DataSource/Id
以及防止DataSource/Id
被更改。我认为抛出异常是一个很好的折衷方案,而不是重新实现所有ListView
的功能。 - Nelson RothermelObjectDataSource
(或类似的东西),但同时也想要一些默认模板。 - Nelson Rothermel您可以隐藏原始实现并返回基础实现:
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所暗示的那样,这是一种有点奇怪的情况。为什么不从一开始就将基类中的setter设置为私有或受保护呢?
Foo f = new Bar(); f.SomeString = "blah"
?这就是我没有选择这条路的原因。由于我是从标准的ASP.NET Web控件派生而来,所以无法更改基类。 - Nelson Rothermel我认为在这种情况下,更好的解决方案是使用新关键字
public class Aclass {
public int ReadOnly { get; set; }
}
public class Bclass : Aclass {
public new int ReadOnly { get; private set; }
}
在C#中,您不允许隐藏基类的公共成员,因为某人可以将派生类的实例塞入对基类的引用中,并以此访问属性。
如果您想创建一个提供另一个类大部分但不是全部接口的类,则必须使用聚合。不要从“基类”继承,而是通过值在“派生”类中包含一个“基类”,并为您希望公开的“基类”功能提供自己的成员函数。然后,这些函数只需将调用转发到相应的“基类”函数即可。
有些情况下,你所描述的模式是合理的。例如,可能有一个抽象类MaybeMutableFoo
,从中派生出子类型MutableFoo
和ImmutableFoo
。这三个类都包括一个IsMutable
属性和方法AsMutable()
、AsNewMutable()
和AsImmutable()
。
在这种情况下,如果MaybeMutableFoo
的契约明确指定setter只有在IsMutable
返回true时才能工作,那么公开读写属性将是完全正确的。一个具有MaybeMutableFoo
类型字段的对象,如果它持有一个ImmutableFoo
实例,除非必须写入该实例,否则可以完全满足于该实例(因为它知道它是不可变的)。当需要写入时,它会用通过AsMutable()
返回的对象替换该字段,然后将其用作可变的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
的只读替换不会改变属性获取的行为;只读版本与基类属性相同地链接到基类方法。以这种方式做事情将确保更改属性getter的补丁点只有一个,更改setter的补丁点也只有一个,即使属性本身被重新定义,这些补丁点也不会改变。