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

为什么不允许使用下面的 C # 代码:

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’没有可重写的集访问器

67701 次浏览

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

在重写时,必须继续支持基础接口。如果您可以添加一个 setter,那么实际上就是添加了一个新方法,就类的接口而言,这个方法对于外部世界来说是不可见的。

的确,添加一个新方法本身并不会破坏兼容性,但是由于它仍然是隐藏的,因此决定不允许这样做是非常有意义的。

因为这会打破封装和实现隐藏的概念。考虑一下这种情况,当您创建一个类并发布它,然后您的类的使用者使自己能够设置一个属性,而您最初只为该属性提供了一个 getter。它将有效地破坏类的任何不变量,您可以在实现中依赖这些不变量。

因为 Baseclass 的作者明确声明 Bar 必须是只读属性。对于派生来说,违反这个契约并使其读写是没有意义的。

我同意微软的说法。
假设我是一个新的程序员,被告知要根据 Basecclass 派生编写代码。我编写了一些假设 Bar 不能被写入的内容(因为 Basecclass 显式地声明它是一个 get only 属性)。 现在有了你的推导,我的密码可能会被破解。

public class BarProvider
{ BaseClass _source;
Bar _currentBar;


public void setSource(BaseClass b)
{
_source = b;
_currentBar = b.Bar;
}


public Bar getBar()
{ return _currentBar;  }
}

由于不能根据 BaseClass 接口设置 Bar,BarProvider 假设缓存是一种安全的做法——因为 Bar 不能被修改。但是如果在派生中可以进行设置,那么如果有人在外部修改 _ source 对象的 Bar 属性,这个类可能会提供过时的值。重点是“ 要开放,避免做偷偷摸摸的事情和让人惊讶的事情

更新 : < em > Ilya Ryzhenkov 问道: “为什么接口不遵循相同的规则呢?” 嗯. . 我越想越觉得混乱。
接口是一个契约,其中规定“期望实现具有一个名为 Bar 的 read 属性”。如果我看到一个接口,我就不太可能做出只读的假设。当我在一个接口上看到一个 get-only 属性时,我将其读作“任何实现都会公开这个属性 Bar”... ... 在一个基类上,它单击为“ Bar 是一个只读属性”。当然严格来说,你没有违反合同。.你做的更多。所以从某种意义上说,你是对的。.最后,我想说的是“尽可能避免出现误解”。

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

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


int IBase.Bar {
get { return Bar; }
}

因为具有只读属性(无 setter)的类可能有充分的理由这样做。例如,可能没有任何底层数据存储。允许您创建 setter 违反了该类设置的约定。只是糟糕的 OOP。

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

你不能这么做:

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

IMO,C # 不应该限制这样的场景,开发人员有责任相应地使用它。

我认为主要原因很简单,因为语法太明确了,不能以其他方式工作。这个代码:

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

非常明确地指出,getset都是重写的。基类中没有 set,因此编译器会抱怨。就像您不能重写未在基类中定义的方法一样,您也不能重写 setter。

你可能会说,编译器应该猜测你的意图,只应用覆盖的方法,可以覆盖(例如,在这种情况下的 getter) ,但这违背了 C # 设计原则之一-编译器不能猜测你的意图,因为它可能猜错了你不知道。

我认为下面的语法可能会做得很好,但是正如 Eric Lippert 一直说的那样,实现这样一个小特性仍然需要大量的工作..。

public int MyProperty
{
override get { ... } // not valid C#
set { ... }
}

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

public int MyProperty { override get; set; } // not valid C#

这不是不可能的。你只需要在属性中使用“ new”关键字。例如,

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

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

希望这个能帮上忙

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

首先,我想说的是,具有 get-only 属性并不一定意味着只读。我将其解释为“从这个接口/抽象类可以得到这个值”,这并不意味着这个接口/抽象类的某些实现不需要用户/程序来显式设置这个值。抽象类用于实现所需的部分功能。我完全看不出继承的类为什么不能在不违反任何契约的情况下添加 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美分”的帖子,但我觉得与原来的海报,并试图合理化,这是一件好事似乎奇怪的我,尤其是考虑到同样的限制不适用于直接从接口继承。

另外,关于使用 new 代替重写的提法在这里也不适用,它根本不起作用,即使起作用了,也不会得到想要的结果,即接口所描述的虚拟 getter。

问题是,不管出于什么原因,微软决定应该有三种不同类型的属性: 只读、只写和读写,其中只有一种可能在给定的上下文中具有给定的签名; 属性可能只会被相同声明的属性覆盖。要实现所需的功能,需要创建两个具有相同名称和签名的属性——其中一个是只读的,另一个是读写的。

就我个人而言,我希望“属性”的整个概念可以被废除,除非类属性的语法可以用作语法糖来调用“ get”和“ set”方法。这不仅方便了“ add set”选项,还允许“ get”返回与“ set”不同的类型。虽然这种能力不会经常使用,但是有时候让“ get”方法返回一个包装器对象是有用的,而“ set”可以接受包装器或实际数据。

基类中的只读属性表示该属性表示总是可以从类中确定的值(例如,与对象的(db -)上下文匹配的枚举值)。因此,确定值的责任保留在类中。

添加 setter 会在这里引起一个棘手的问题: 如果将该值设置为已有的单个可能值以外的任何值,则应发生验证错误。

不过,规则通常也有例外。很有可能,例如,在一个派生类中,上下文将可能的枚举值缩小到10个中的3个,但是这个对象的用户仍然需要决定哪个是正确的。派生类需要将确定值的责任委托给此对象的用户。 要实现 ,重要的是这个对象 应该很好地意识到这个例外的用户并承担设置正确值的责任。

在这种情况下,我的解决方案是保持属性为只读,并向派生类添加一个新的读写属性以支持异常。 重写原始属性只会返回新属性的值。 新属性可以有正确的名称,指示此异常的上下文。

这也支持了《渐书》的一句名言: “尽可能使误解难以出现”。

我同意不能在派生类型中重写 getter 是一种反模式。Read-Only 指定缺乏实现,而不是一个纯函数的契约(由最高投票答案暗示)。

我怀疑微软之所以有这种局限性,要么是因为同样的错误观念得到了推广,要么是因为简化了语法; 不过,既然范围可以应用于单独获取或设置,也许我们可以希望覆盖也可以这样做。

最受欢迎的答案表明,只读属性应该比读/写属性更“纯”,这种错误观念是荒谬的。只需查看框架中许多常见的只读属性; 该值不是常量/纯函数; 例如,DateTime。现在是只读的,但不是纯函数值。试图“缓存”一个只读属性的值,假设它下次将返回相同的值,这是有风险的。

无论如何,我已经使用了以下其中一种策略来克服这种局限性; 这两种策略都不够完美,但会让你一瘸一拐地克服这种语言缺陷:

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

为了实现这一点,下面是一个使用反射的解决方案:

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

这样我们就可以在只读属性上设置对象值。不过可能在所有情况下都不管用!

有可能。

如果愿意,你可以用 setter 来覆盖 get-only 方法,基本上就是:

  1. 使用相同的名称创建同时具有 getsetnew属性。

  2. 以前的 get改名为新的 get

这使我们能够用 get/set覆盖属性,即使它们在基本定义中缺少 setter。


情况: 预先存在的仅 get属性。

有些类结构是无法修改的。也许它只是一个类,或者它是一个预先存在的继承树。无论是哪种情况,您都希望向属性添加 set方法,但是不能。

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

问题: 不能 overrideget-只与 get/set

您希望 override具有 get/set属性,但是它无法编译。

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

解决方案: 使用 abstract中间层。

虽然你不能直接使用 get/set属性的 override,但是你可以使用 可以:

  1. 创建具有相同名称的 new get/set属性。

  2. 旧的 get方法用一个访问器来访问新的 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; }
}

然后,编写之前无法编译的类。这次将编译它,因为您实际上并没有使用 get-only 属性进行 override; 而是使用 new关键字替换它。

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

结果: 一切正常!

var d = new D();


var a = d as A;
var b = d as B;
var c = d as C;


Print(a.X);      // Prints "0", the default value of an int.
Print(b.X);      // Prints "0", the default value of an int.
Print(c.X);      // Prints "0", the default value of an int.
Print(d.X);      // Prints "0", the default value of an int.


// a.X = 7;      // Won't compile: A.X doesn't have a setter.
// b.X = 7;      // Won't compile: B.X doesn't have a setter.
// c.X = 7;      // Won't compile: C.X doesn't have a setter.
d.X = 7;         // Compiles, because D.X does have a setter.


Print(a.X);      // Prints "7", because 7 was set through D.X.
Print(b.X);      // Prints "7", because 7 was set through D.X.
Print(c.X);      // Prints "7", because 7 was set through D.X.
Print(d.X);      // Prints "7", because 7 was set through D.X.

讨论。

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

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

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

主要的缺点是需要编写更多的代码,并且在继承树中有额外的 abstract class。对于接受参数的构造函数来说,这可能有点烦人,因为这些参数必须在中间层中进行复制/粘贴。


奖励: 您可以更改属性的返回类型。

另外,如果需要,还可以更改返回类型。

  • 如果基本定义仅为 get,则可以使用派生程度更高的返回类型。

  • 如果基本定义仅为 set,则可以使用派生程度较低的返回类型。

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

    • 可以使用派生类型更多的返回类型 如果,使其仅为 set;

    • 可以使用派生程度较低的返回类型 如果,使其仅为 get

在所有情况下,如果需要,可以保持相同的返回类型。

您应该更改问题的标题,使其具体到您的问题仅涉及重写抽象属性,或者您的问题涉及通常重写类的 get-only 属性。


如果前者 (覆盖抽象属性)

那个密码没用。单独的基类不应该告诉您必须重写 Get-Only 属性(可能是 Interface)。基类提供通用的功能,这些功能可能需要来自实现类的特定输入。因此,公共功能可以调用抽象属性或方法。在给定的情况下,常见的功能性方法应该要求您覆盖抽象方法,例如:

public int GetBar(){}

但是如果你对此没有控制,并且基类的功能从它自己的公共属性读取(奇怪) ,那么就这样做:

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

如果后者 (覆盖类的 get-only 属性)

每个非抽象属性都有一个 setter。否则它就没用了,你也不该用它。微软不一定允许你做你想做的事情。原因是: 塞特存在于某种形式或另一种,你可以很容易地完成你想要的 维里

基类,或者可以用 {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,因为您不希望基类处理它,不能访问它是如何处理它的,或者被赋予了违背您的意愿非常快速地处理一些代码的任务。

后者与前者 (结论)

长话短说: 微软没有必要做出任何改变。您可以选择如何设置实现类,并且在不使用构造函数的情况下,使用所有基类或不使用基类。

解决方案只是用例的一小部分,但是: 在 C # 6.0中,“ readonly”setter 被自动添加为重写的 getter-only 属性。

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


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


public ConcreteClass(int bar)
{
Bar = bar;
}
}