2010-12-09 106 views
5

我重写了派生类中的一个属性,我只想使其只读。 C#编译器不会让我更改访问修饰符,因此它必须保持公开。在派生类中使属性只读

这样做的最好方法是什么?我是否应该在set { }中投掷InvalidOperationException

+0

我们需要更多的方面,我想。 – Noldorin 2010-12-09 22:53:55

回答

8

让派生者在派生类中抛出InvalidOperationException违反了Liskov Subsitution Principle。基本上使得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

+0

因此,如果基类有一个`IsReadOnly`属性并引发它自己的异常,那么它可以吗?这就是`NameObjectCollectionBase`>`NameValueCollection`>`HttpValueCollection`继承的工作原理,它被`Page`的`Request.Param`使用。我理解这个原则背后的原因,但是对于`NameObjectCollectionBase`来说,它与你的陈述相矛盾:`如果setter在所有情况下都不合适,那么它就不属于基类。`不幸的是我正在使用a。没有实现这个(很好的理由)的NET类。我认为这对我来说是有道理的。 – 2010-12-09 23:18:56

+1

@Nelson的基类有`IsReadOnly` ......是一个妥协。就我个人而言,我认为这是一种可怕的模式,如果将集合正确划分为可变和只读接口/类型,BCL将会更好。 – JaredPar 2010-12-09 23:20:39

+0

好吧,我会更具体。我从ASP.NET的`ListView`派生。我们称之为“MyCustomList”。我想要ListView的所有特性/模板,但是让MyCustomList处理数据,分配DataSource/Id,并防止DataSource/Id被改变。我认为抛出一个异常是一个很好的折衷,而不是自己重新实现所有的`ListView`功能。 – 2010-12-09 23:26:30

3

可以隐藏原来的实现并返回基实现:

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 
     } 
    } 
} 

但是,正如贾里德所暗示的,这是一种奇怪的情况。你为什么不让你的setter私人化或者在基类中保护起步?

0

您不允许在C#中隐藏您的基类的公共成员,因为有人可能会获取您的派生类的实例,将其填入对基类的引用中,并以此方式访问属性。

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

0

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

0

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

在这种情况下,如果MaybeMutableFoo显示读写属性是完全正确的,如果其合约明确指定设置器可能无法工作,除非IsMutable返回true。具有MaybeMutableFoo类型的字段的对象可能对该实例非常满意,除非或直到必须写入该对象,因此它将用通过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的只读替换不会更改属性get的行为;只读版本链与基类属性的基类方法相同。这样做可以确保有一个用于更改属性获取器的补丁点,另一个用于更改设置器,即使属性本身被重新定义,这些补丁点也不会更改。

1

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

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

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