151

为什么不允许使用以下 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' 没有可覆盖的 set 访问器

4

16 回答 16

43

我认为主要原因只是语法过于明确,无法以任何其他方式工作。这段代码:

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

非常明确的是 theget和 theset都是覆盖。基类中没有set,所以编译器会抱怨。就像您不能覆盖未在基类中定义的方法一样,您也不能覆盖 setter。

您可能会说编译器应该猜测您的意图,并且仅将覆盖应用于可以被覆盖的方法(即本例中的 getter),但这违反了 C# 设计原则之一——编译器不得猜测您的意图,因为它可能会在你不知情的情况下猜错。

我认为下面的语法可能会做得很好,但正如 Eric Lippert 一直说的那样,即使是实现这样的小功能仍然需要大量的努力......

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

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

public int MyProperty { override get; set; } // not valid C#
于 2010-04-03T12:06:27.693 回答
27
于 2014-03-05T22:07:38.667 回答
19

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

首先,我想争辩说,拥有一个 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 而不是 override 的提及在这里不适用,它根本不起作用,即使它这样做也不会给你想要的结果,即接口描述的虚拟 getter。

于 2010-09-09T16:18:37.873 回答
11

我同意不能覆盖派生类型中的 getter 是一种反模式。Read-Only 指定缺乏实现,而不是纯功能的合同(由最高投票答案暗示)。

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

最高投票答案所表明的误解,即只读属性应该以某种方式比读/写属性更“纯”是荒谬的。简单看一下框架中很多常见的只读属性;该值不是常数/纯函数;例如, DateTime.Now 是只读的,但不是纯函数值。尝试“缓存”只读属性的值,假设它下次将返回相同的值是有风险的。

无论如何,我使用以下策略之一来克服此限制;两者都不完美,但可以让你克服这种语言缺陷:

   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;
   }
于 2012-09-13T20:50:42.577 回答
4

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

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

int IBase.Bar { 
  get { return Bar; }
}
于 2008-09-17T12:37:06.807 回答
1

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

你不能做这样的事情:

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

IMO,C# 不应该限制这种情况。开发人员有责任相应地使用它。

于 2008-09-20T11:03:32.240 回答
1

问题是,无论出于何种原因,微软决定应该存在三种不同类型的属性:只读、只写和读写,在给定的上下文中,只有一个可能存在给定签名;属性只能被相同声明的属性覆盖。要执行您想要的操作,有必要创建两个具有相同名称和签名的属性——其中一个是只读的,另一个是读写的。

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

于 2011-12-19T02:49:49.500 回答
0

这是一个解决方法,以使用反射实现此目的:

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

这样我们可以在只读属性上设置对象值。但可能不适用于所有场景!

于 2013-06-24T04:03:49.757 回答
0

您应该将问题标题更改为详细说明您的问题仅与覆盖抽象属性有关,或者您的问题通常与覆盖类的 get-only 属性有关。


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

那个代码没用。单独的基类不应该告诉您您被迫覆盖 Get-Only 属性(可能是接口)。基类提供可能需要来自实现类的特定输入的通用功能。因此,通用功能可能会调用抽象属性或方法。在给定的情况下,常见的功能方法应该要求您覆盖抽象方法,例如:

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。否则它是无用的,你不应该关心使用它。微软不必让你做你想做的事。原因是:setter 以某种形式存在,你可以轻松地完成你想要的 Veerryy

基类或任何您可以使用 读取属性的类都{get;}具有某种针对该属性的公开设置器。元数据将如下所示:

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,因为您不希望基类处理它,无权查看它是如何处理它的,或者被要求违背您的意愿对一些代码进行真正的快速'n'dirty .

在后者和前者(结论)

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

于 2015-10-22T20:13:49.750 回答
0

仅针对一小部分用例的解决方案,但是:在 C# 6.0 中,“只读”setter 会自动添加用于覆盖的仅 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;
    }
}
于 2017-10-09T17:28:10.477 回答
-1

因为那会破坏封装和实现隐藏的概念。考虑一下当你创建一个类,发布它,然后你的类的消费者让他自己能够设置一个你最初只为其提供一个 getter 的属性的情况。它会有效地破坏您在实现中可以依赖的类的任何不变量。

于 2008-09-17T12:17:39.617 回答
-1

这并非不可能。您只需在属性中使用“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”打破了基类实现和子类实现之间的契约。当一个类可以有多个契约(通过接口或基类)时,这是必要的。

希望这可以帮助

于 2010-08-21T20:06:44.347 回答
-1

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

在此处添加 setter 会导致一个尴尬的问题:如果您将值设置为它已经拥有的单个可能值之外的任何其他值,则会发生验证错误。

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

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

这也支持了 Gishu 的有效言论:“尽可能避免误解”。

于 2012-07-13T12:42:15.073 回答
-2

因为具有只读属性(没有设置器)的类可能有充分的理由。例如,可能没有任何底层数据存储。允许你创建一个 setter 会破坏类的约定。这只是糟糕的面向对象。

于 2008-09-17T13:15:24.703 回答
-3

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

覆盖时,您必须继续支持底层接口。如果您可以添加一个 setter,那么您将有效地添加一个新方法,就您的类的接口而言,该方法对外部世界是不可见的。

诚然,添加新方法本身不会破坏兼容性,但由于它会保持隐藏状态,因此决定禁止这样做是完全合理的。

于 2008-09-17T12:17:11.783 回答
-7

因为 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 假设缓存是安全的 - 因为 Bar 无法修改。但是,如果在派生中可以设置,则如果有人在外部修改了 _source 对象的 Bar 属性,则此类可能会提供陈旧的值。关键是'保持开放,避免做鬼鬼祟祟的事情和令人惊讶的事情'

更新Ilya Ryzhenkov 问道“那为什么界面不按照相同的规则运行呢?” 嗯..当我想到它时,这变得更加混乱。
接口是一个契约,它说“期望一个实现有一个名为 Bar 的读取属性。” 如果我看到一个接口,就个人而言,我不太可能做出只读的假设。当我在接口上看到一个 get-only 属性时,我将其读作“任何实现都会公开此属性 Bar”......在基类上它单击为“Bar 是一个只读属性”。当然,从技术上讲,您并没有违反合同..您做得更多。所以从某种意义上说,你是对的。我最后会说“尽可能避免出现误解”。

于 2008-09-17T12:17:54.047 回答