为什么不可能覆盖只有getter属性并添加setter?

你为什么认为(或者说,为什么这么好)微软select不允许:

public abstract class BaseClass { public abstract int Bar { get;} } public class ConcreteClass : BaseClass { public override int Bar { get { return 0; } set {} } } 

CS0546“ConcreteClass.Bar.set”:无法重写,因为“BaseClass.Bar”没有可覆盖的set访问器

因为Baseclass的作者已经明确地声明Bar必须是只读属性。 派生违反合同并使其可读写是没有意义的。

我和微软在这一个。
比方说,我是一个新的程序员,他被告知要对Baseclass派生进行编码。 我写了一些假设Bar不能被写入的东西(因为Baseclass明确指出它是一个只有属性)。 现在你的派生,我的代码可能会中断。 例如

 public class BarProvider { BaseClass _source; Bar _currentBar; public void setSource(BaseClass b) { _source = b; _currentBar = b.Bar; } public Bar getBar() { return _currentBar; } } 

由于Bar不能根据BaseClass接口进行设置,因此BarProvider认为caching是一件安全的事情 – 因为Bar不能被修改。 但是如果在派生中可以设置,那么如果有人在外部修改了_source对象的Bar属性,这个类可能会提供陈旧的值。 要“ 开放,避免做鬼鬼祟祟的事情和令人惊讶的人

更新伊里亚·Ryzhenkov问“为什么不接口按照相同的规则发挥呢? 呃..这个我觉得这个比较混乱
一个接口是一个契约,说'期望一个实现有一个名为Bar的读取属性。 个人而言 ,如果我看到一个接口,我不太可能做出只读的假设。 当我在接口上看到一个只读属性时,我将其读为“任何实现都将公开此属性栏…”,并将其作为“Bar是只读属性”单击的基类中。 当然,从技术上说,你并没有违约,你做的更多。 所以,你是对的,我只想说“尽可能地让误解发生”。

我认为主要的原因就是语法过于明确,不能以其他方式工作。 此代码:

 public override int MyProperty { get { ... } set { ... } } 

很明显, getset都是重写。 在基类中没有set ,所以编译器抱怨。 就像你不能覆盖没有在基类中定义的方法一样,你也不能重写setter。

你可能会说编译器应该猜测你的意图,并且只能将覆盖应用到可以被覆盖的方法(即在这种情况下是getter),但这违背了C#devise原则之一 – 编译器不能猜测你的意图,因为如果不知道,可能会猜错。

我认为下面的语法可能会做得很好,但正如Eric Lippert所说的,实现这样一个小function仍然是一个很大的努力…

 public int MyProperty { override get { ... } set { ... } } 

或者,对于自动实现的属性,

 public int MyProperty { override get; set; } 

我今天偶然发现了同样的问题,我想有一个非常合理的理由要这样做。

首先,我想争辩说,拥有只读属性并不一定会转换为只读属性。 我把它解释为“从这个接口/ abtract你可以得到这个值”,这并不意味着该接口/抽象类的某些实现将不需要用户/程序明确地设置该值。 抽象类实现了部分所需function的目的。 我完全没有理由为什么一个inheritance的类在不违反任何契约的情况下不能添加一个setter。

以下是我今天需要的一个简单例子。 为了解决这个问题,我最终不得不在界面中添加一个setter。 添加setter而不添加SetProp方法的原因是接口的一个特定实现使用DataContract / DataMember来进行Prop的序列化,如果为了这个目的我必须添加另一个属性,那么这将会变得不必要的复杂序列化。

 interface ITest { // Other stuff string Prop { get; } } // Implements other stuff abstract class ATest : ITest { abstract public string Prop { get; } } // This implementation of ITest needs the user to set the value of Prop class BTest : ATest { string foo = "BTest"; public override string Prop { get { return foo; } set { foo = value; } // Not allowed. 'BTest.Prop.set': cannot override because 'ATest.Prop' does not have an overridable set accessor } } // This implementation of ITest generates the value for Prop itself class CTest : ATest { string foo = "CTest"; public override string Prop { get { return foo; } // set; // Not needed } } 

我知道这只是一个“我的2美分”的职位,但我觉得与原来的海报,并试图合理化,这是一件好事,似乎很奇怪,特别是考虑到相同的限制不适用时,直接inheritance接口。

另外提到使用新的而不是覆盖不适用在这里,它根本不工作,即使它不会给你想要的结果,即一个虚拟的getter接口描述。

我同意不能在派生types中重写getter是一种反模式。 只读指定缺less实现,而不是纯粹function的合同(隐含在最高票数答案中)。

我怀疑微软有这个限制,或者是因为同样的错误观念被推广,或者是因为语法的简化; 不过,现在这个范围可以单独应用来获取或设置,也许我们可以希望覆盖也可以。

最高票的答案表明,只读属性应该比读写属性更“纯”,这是荒谬的。 只需查看框架中的许多常见只读属性即可; 价值不是一个常数/纯粹的function; 例如,DateTime.Now是只读的,但不是纯粹的function值。 假设下一次返回相同的值,试图“caching”只读属性的值是有风险的。

无论如何,我已经使用了以下策略之一来克服这个限制; 两者都不是完美的,但会让你跛行超越这种语言的不足:

  class BaseType { public virtual T LastRequest { get {...} } } class DerivedTypeStrategy1 { /// get or set the value returned by the LastRequest property. public bool T LastRequestValue { get; set; } public override T LastRequest { get { return LastRequestValue; } } } class DerivedTypeStrategy2 { /// set the value returned by the LastRequest property. public bool SetLastRequest( T value ) { this._x = value; } public override T LastRequest { get { return _x; } } private bool _x; } 

这是可能的

tl; dr如果需要,您可以使用setter重写一个只读方法。 基本上只是:

  1. 创build一个同时具有getsetnew属性。

  2. 如果你什么都不做,那么当派生类通过它的基types被调用时,旧的get方法仍然会被调用。 为了解决这个问题,在旧的get方法中添加一个abstract中间层来强制它返回新的get方法的结果。

这使我们可以通过get / set来覆盖属性,即使它们的基本定义中缺less一个。

作为奖励,您也可以根据需要更改退货types。

  • 如果基本定义是只读的,那么你可以使用更多派生的返回types。

  • 如果只set了基本定义,那么您可以使用派生得较less的返回types。

  • 如果基本定义已经get set ,那么:

    • 你可以使用更衍生的返回types, 如果你只set它;

    • 你可以使用派生得较less的返回types。

在所有情况下,如果您愿意,可以保持相同的返回types。 为简单起见,以下示例使用相同的返回types。

情况:已有的get资产

你有一些你不能修改的类结构。 也许这只是一个类,或者它是一个预先存在的inheritance树。 无论如何,你想添加一个方法到一个属性,但不能。

 public abstract class A { public abstract int X { get; } // You want a setter, but can't add it. } public class B : A { public override int X { get { return 0; } } } 

问题:不能用

你想用get / set属性override ,但是不会编译。

 public class C : B { private int _x; public override int X { get { return _x; } set { _x = value; } // Won't compile } } 

解决scheme:使用abstract中间层

虽然不能直接用get / set属性override ,但可以

  1. 使用相同的名称创build一个new get / set属性。

  2. 用新的get方法的访问器override旧的get方法以确保一致性。

所以,首先你写下abstract中间层:

 public abstract class C : B { // Seal off the old getter. From now on, its only job // is to alias the new getter in the base classes. public sealed override int X { get { return this.XGetter; } } protected abstract int XGetter { get; } } 

然后,你编写以前不能编译的类。 它会编译这次,因为你实际上并没有override get only属性; 而是使用new关键字replace它。

 public class D : C { private int _x; public new virtual int X { get { return this._x; } set { this._x = value; } } // Ensure base classes (A,B,C) use the new get method. protected sealed override int XGetter { get { return this.X; } } } 

结果:一切正常!

很明显,这是为D预期的。

 var test = new D(); Print(test.X); // Prints "0", the default value of an int. test.X = 7; Print(test.X); // Prints "7", as intended. 

当把D视为其基类之一时,所有东西仍然按照预期工作,例如AB 但是,它的原因可能不那么明显。

 var test = new D() as B; //test.X = 7; // This won't compile, because test looks like a B, // and B still doesn't provide a visible setter. 

然而, get的基类定义仍然最终被派生类的get定义覆盖,所以它仍然是完全一致的。

 var test = new D(); Print(test.X); // Prints "0", the default value of an int. var baseTest = test as A; Print(test.X); // Prints "7", as intended. 

讨论

此方法允许您将set方法添加到get -only属性。 你也可以用它来做这样的事情:

  1. 将任何属性更改为get only, set only或get and- set属性,而不pipe它在基类中是什么。

  2. 更改派生类中方法的返回types。

主要的缺点是有更多的代码要做,在inheritance树中有一个额外的abstract class 。 这对于带参数的构造函数可能有些恼人,因为这些构造函数必须在中间层进行复制/粘贴。

你也许可以通过创build一个新的属性来解决这个问题:

 public new int Bar { get { return 0; } set {} } int IBase.Bar { get { return Bar; } } 

我可以理解你所有的观点,但实际上,C#3.0的自动属性在这种情况下是无用的。

你不能这样做:

 public class ConcreteClass : BaseClass { public override int Bar { get; private set; } } 

国际海事组织,C#不应该限制这种情况。 开发人员有责任相应使用它。

问题在于,无论出于什么原因,微软决定应该有三种不同types的属性:只读,只写和读写,在给定的上下文中,只有其中一种可能存在给定的签​​名; 属性只能被相同声明的属性覆盖。 要做你想做的事情,有必要创build两个具有相同名称和签名的属性 – 其中一个是只读的,另一个是读写的。

就个人而言,我希望能够取消“属性”的整个概念,除了属性语法可以用作句法糖来调用“get”和“set”方法。 这不仅会促进“添加设置”选项,而且还会允许“get”从“set”返回不同的types。 虽然这样的function不会被经常使用,但有时可以使用'get'方法返回一个包装对象,而'set'可以接受包装器或实际数据。

这是一个解决方法,以实现这个使用reflection:

 var UpdatedGiftItem = // object value to update; foreach (var proInfo in UpdatedGiftItem.GetType().GetProperties()) { var updatedValue = proInfo.GetValue(UpdatedGiftItem, null); var targetpropInfo = this.GiftItem.GetType().GetProperty(proInfo.Name); targetpropInfo.SetValue(this.GiftItem, updatedValue,null); } 

这样我们可以在一个只读的属性上设置对象的值。 虽然在所有的情况下可能不工作!

你应该改变你的问题标题,或者你的问题仅仅是关于重写一个抽象属性,或者你的问题是关于一般重写一个类的只读属性。


如果前者 (凌驾抽象属性)

该代码是无用的。 单独一个基类不应该告诉你,你不得不重写一个Get-Only属性(也许是一个接口)。 基类提供可能需要来自实现类的特定input的通用function。 因此,通用function可能会调用抽象属性或方法。 在给定的情况下,通用的function方法应该是要求你重写一个抽象方法,例如:

 public int GetBar(){} 

但是如果你不能控制它,而且基类的function从它自己的公共属性(奇怪的)中读取,那么就这样做:

 public abstract class BaseClass { public abstract int Bar { get; } } public class ConcreteClass : BaseClass { private int _bar; public override int Bar { get { return _bar; } } public void SetBar(int value) { _bar = value; } } 

我想指出(怪异的)评论:我认为最好的做法是让一个类不使用自己的公共属性,而是在它们存在时使用它的私有/保护字段。 所以这是一个更好的模式:

 public abstract class BaseClass { protected int _bar; public int Bar { get { return _bar; } } protected void DoBaseStuff() { SetBar(); //Do something with _bar; } protected abstract void SetBar(); } public class ConcreteClass : BaseClass { protected override void SetBar() { _bar = 5; } } 

如果后者 (重写一个类的只可获得的属性)

每个非抽象的属性都有一个setter。 否则它是无用的,你不应该使用它。 微软不必让你做你想做的事情。 原因是:二传手以某种forms存在,你可以很容易地完成你想要的Veeyy

基类或任何可以使用{get;}读取属性的类,都具有某种暴露的setter属性。 元数据将如下所示:

 public abstract class BaseClass { public int Bar { get; } } 

但是实现将具有复杂性的两个端点:

最小的复杂:

 public abstract class BaseClass { private int _bar; public int Bar { get{ return _bar; }} public void SetBar(int value) { _bar = value; } } 

最复杂的:

 public abstract class BaseClass { private int _foo; private int _baz; private int _wtf; private int _kthx; private int _lawl; public int Bar { get { return _foo * _baz + _kthx; } } public bool TryDoSomethingBaz(MyEnum whatever, int input) { switch (whatever) { case MyEnum.lol: _baz = _lawl + input; return true; case MyEnum.wtf: _baz = _wtf * input; break; } return false; } public void TryBlowThingsUp(DateTime when) { //Some Crazy Madeup Code _kthx = DaysSinceEaster(when); } public int DaysSinceEaster(DateTime when) { return 2; //<-- calculations } } public enum MyEnum { lol, wtf, } 

我的观点是,无论哪种方式,你都有接触者。 在你的情况下,你可能想要重写int Bar因为你不希望基类来处理它,没有权限检查它是如何处理的,或者被委托给一些真正快速的代码你的意愿。

无论是后者还是前者 (结论)

长话短说:微软没有必要改变任何事情。 你可以select你的实现类是如何设置的,并且不使用构造函数,而是使用全部或者全部的基类。

解决scheme仅用于一小部分用例,但是:在C#6.0中,“只读”设置程序会自动添加用于重写的仅限getter属性。

 public abstract class BaseClass { public abstract int Bar { get; } } public class ConcreteClass : BaseClass { public override int Bar { get; } public ConcreteClass(int bar) { Bar = bar; } } 

因为在IL级别,读/写属性转换为两个(getter和setter)方法。

重写时,您必须继续支持底层接口。 如果你可以添加一个setter,那么就可以有效地添加一个新的方法,就你的类的接口而言,这个方法对于外界是不可见的。

诚然,增加一种新方法本身并不会破坏兼容性,但是由于它将保持隐藏,所以决定禁止这一点是非常有意义的。

因为这会打破封装和执行隐藏的概念。 考虑一下当你创build一个类,运送它,然后你的类的消费者使自己能够设置一个你最初只提供一个吸气剂的属性的情况。 它会有效地破坏你的类的任何不变式,你可以依赖于你的实现。

因为具有只读属性(无setter)的类可能有很好的理由。 例如,可能没有任何基础数据存储。 允许你创build一个二传手破坏了class级制定的合同。 这只是糟糕的OOP。

这不是不可能的。 您只需在您的财产中使用“新”关键字。 例如,

 namespace { public class Base { private int _baseProperty = 0; public virtual int BaseProperty { get { return _baseProperty; } } } public class Test : Base { private int _testBaseProperty = 5; public new int BaseProperty { get { return _testBaseProperty; } set { _testBaseProperty = value; } } } } 

看来这种方法似乎满足了这个讨论的两个方面。 使用“新”打破了基类实现和子类实现之间的契约。 当一个类可以有多个契约(通过接口或基类)时,这是必要的。

希望这可以帮助

基类中的只读属性指示此属性表示始终可以从类内部确定的值(例如,与对象的(db-)上下文匹配的枚举值)。 所以确定价值的责任在这个阶级中。

添加一个setter会在这里引起一个棘手的问题:如果你将值设置为除了它已有的单个可能的值之外的其他值,就会发生validation错误。

规则通常有例外,但。 例如在一个派生类中,上下文将可能的枚举值缩小到3分之10,这个对象的用户仍然需要决定哪一个是正确的。 派生类需要将确定值的责任委托给这个对象的用户。 重要的是要认识到这个对象的用户应该清楚这个exception,并承担责任来设置正确的值。

我在这种情况下的解决scheme是将属性保持为只读,并将新的读写属性添加到派生类以支持该exception。 原始属性的覆盖将简单地返回新属性的值。 新的属性可以有适当的名称来正确指示这个exception的上下文。

This also supports the valid remark: "make it as hard as possible for misunderstandings to crop up" by Gishu.