为什么不能覆盖仅限getter的属性并添加setter?

时间:2008-09-17 12:14:05

标签: c# .net properties getter-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'没有可覆盖的set accessor

16 个答案:

答案 0 :(得分:34)

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

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

非常清楚getset都是覆盖。基类中没有set,因此编译器会抱怨。就像你不能覆盖未在基类中定义的方法一样,你也不能覆盖一个setter。

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

我认为以下语法可能做得很好,但正如Eric Lippert所说,实现这样的小功能仍然是一项重大工作......

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

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

public int MyProperty { override get; set; }

答案 1 :(得分:19)

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

首先,我想说拥有一个只获取属性并不一定会转换为只读属性。我把它解释为“从这个接口/ abtract你可以得到这个值”,这并不意味着该接口/抽象类的某些实现不需要用户/程序显式设置该值。抽象类用于实现所需功能的一部分。我完全没有理由认为继承的类在没有违反任何合同的情况下无法添加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。

答案 2 :(得分:13)

可能

tl; dr - 如果需要,可以使用setter覆盖get-only方法。它基本上只是:

  1. 使用相同名称创建同时包含newget的{​​{1}}媒体资源。

  2. 如果您不执行任何其他操作,则在通过其基类型调用派生类时,仍会调用旧的set方法。要解决此问题,请在旧的get方法中添加使用abstract的{​​{1}}中间层,以强制它返回新的override方法的结果。

  3. 这使我们可以使用get / get覆盖属性,即使它们在基本定义中缺少一个属性。

    作为奖励,您还可以根据需要更改返回类型。

    • 如果基本定义仅为get - 那么您可以使用更多派生的返回类型。

    • 如果基本定义仅为set - 那么您可以使用较少派生的返回类型。

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

      • 您可以使用更多派生的返回类型 ,如果 ,则仅使用get;

      • 您可以使用较少派生的返回类型 ,如果 ,则仅使用set

    在所有情况下,如果需要,您可以保持相同的返回类型。为简单起见,下面的示例使用相同的返回类型。

    情况:预先存在的set - 仅属性

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

    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; } } } - 仅限override / get

    您希望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 / override媒体资源,但可以

    1. 创建一个名称相同的get set / new媒体资源。

    2. getset方法,其中包含新override方法的访问权限,以确保一致性。

    3. 所以,首先你要编写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; } } - 只有属性;相反,您要使用override关键字替换它。

      get

      结果:一切正常!

      显然,这适用于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; } }
      }
      

      在将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。但是,它起作用的原因可能不太明显。

      A

      但是,B的基类定义仍然最终被派生类的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. - 仅属性。您也可以使用它来执行以下操作:

      1. 将任何属性更改为仅set - 仅get - 或get - 和 - set属性,无论其属性如何基类。

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

      3. 主要的缺点是在继承树中需要做更多的编码和额外的get。对于带参数的构造函数,这可能有点烦人,因为那些必须在中间层中进行复制/粘贴。

答案 3 :(得分:9)

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

我怀疑微软有这种限制要么是因为同样的误解得到了促进,要么是因为简化了语法;但是,既然范围可以应用于单独获取或设置,也许我们可以希望覆盖也可以。

最高投票回答表明的误解是,只读属性应该比读/写属性更“纯粹”,这是荒谬的。只需查看框架中的许多常见只读属性;价值不是恒定/纯粹的功能;例如,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;
   }

答案 4 :(得分:1)

因为Baseclass的编写者明确声明Bar必须是只读属性。对于违反此合同并使其成为可读写的派生来说没有意义。

我和微软就此问题 假设我是一名新程序员,他被告知要对基类推导进行编码。我写的东西假定Bar不能被写入(因为Baseclass明确声明它是一个get get属性)。 现在有了你的推导,我的代码可能会破坏。 e.g。

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属性,则此类可能会提供过时值。关键是'要开放,避免做鬼鬼祟祟的事情和令人惊讶的人'

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

答案 5 :(得分:1)

您可以通过创建新属性来解决问题:

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

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

答案 6 :(得分:1)

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

你不能做那样的事情:

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

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

答案 7 :(得分:1)

问题是,无论出于何种原因,Microsoft决定应该有三种不同类型的属性:只读,只写和读写,在给定的上下文中只有一个可能与给定签名一起存在;属性只能由相同声明的属性覆盖。要做你想做的事,有必要创建两个具有相同名称和签名的属性 - 其中一个是只读的,其中一个是读写的。

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

答案 8 :(得分: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);
}

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

答案 9 :(得分:0)

您应该将问题标题更改为您的问题仅涉及覆盖抽象财产的详细信息,或者您的问题是关于通常覆盖课程的只获取属性的问题。

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

那段代码没用。单独一个基类不应该告诉你,你被迫覆盖了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;}读取属性的任何类,对该属性具有某些类型的公开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,因为您不希望基类处理它,无法查看它是如何处理它的,或者是其任务匆匆忙忙地对某些代码进行快速反对。

后者和前者(结论)

长篇短文:微软没有必要改变任何事情。您可以选择如何设置实现类,并且不使用构造函数,使用全部或不使用基类。

答案 10 :(得分:0)

仅针对一小部分用例的解决方案,但仍然是:在C#6.0&#34; readonly&#34;自动添加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;
    }
}

答案 11 :(得分:-1)

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

覆盖时,您必须继续支持底层接口。如果你可以添加一个setter,那么就你的类的接口而言,你实际上会添加一个对外界不可见的新方法。

是的,添加一种新方法本身并不会破坏兼容性,但由于它会保持隐藏状态,因此决定禁止这种做法非常有意义。

答案 12 :(得分:-1)

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

答案 13 :(得分:-1)

因为具有只读属性(没有setter)的类可能有充分的理由。例如,可能没有任何基础数据存储区。允许您创建一个setter会违反该类规定的合同。这只是糟糕的OOP。

答案 14 :(得分:-1)

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

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

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

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

这也支持有效的评论:“让Gishu尽可能地解决误解”。

答案 15 :(得分:-2)

这不是不可能的。您只需在属性中使用“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可以有多个契约(通过接口或基类)时,这是必要的。

希望这有帮助