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

rip*_*234 139 .net c# 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 {}
    }
}
Run Code Online (Sandbox Code Playgroud)

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

Rom*_*kov 37

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

public override int MyProperty { get { ... } set { ... } }
Run Code Online (Sandbox Code Playgroud)

是相当明确的,无论是getset是覆盖.set基类中没有,所以编译器会抱怨.就像你不能覆盖未在基类中定义的方法一样,也不能覆盖setter.

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

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

public int MyProperty
{
    override get { ... }
    set { ... }
}
Run Code Online (Sandbox Code Playgroud)

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

public int MyProperty { override get; set; }
Run Code Online (Sandbox Code Playgroud)

  • @logeshpalani98 是的。目前这在 C# 中是不可能的。我的例子都是虚构的。 (2认同)

JJJ*_*JJJ 19

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

首先,我想争辩说,拥有一个get-only属性并不一定会转换为只读属性.我把它解释为"从这个接口/ 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
    }
}
Run Code Online (Sandbox Code Playgroud)

我知道这只是一个"我的2美分"的帖子,但我觉得原版海报并试图理性化这对我来说是件好事,特别是考虑到当直接从一个继承时同样的限制不适用接口.

关于使用new而不是override的提及在这里不适用,它根本不起作用,即使它确实不会给你想要的结果,即接口所描述的虚拟getter.

  • 或者,就我而言,{get; 私人集; }.我没有违反任何合同,因为该套装仍然是私人的,只关注衍生类. (2认同)

Nat*_*Nat 15

这是可能的

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

  1. 创建一个new既包含a getset使用相同名称的属性.

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

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

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

  • 如果基本定义是get-only,那么您可以使用更派生的返回类型.

  • 如果基本定义是set-only,那么您可以使用较少派生的返回类型.

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

    • 您可以使用更衍生的返回类型,如果你把它set-only;

    • 您可以使用衍生少返回类型,如果你把它get-only.

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

情况:预先存在的 - 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; } }
}
Run Code Online (Sandbox Code Playgroud)

问题:无法overrideget-only与get/set

你想override使用get/ setproperty,但它不会编译.

public class C : B
{
    private int _x;
    public override int X
    {
        get { return _x; }
        set { _x = value; }   //  Won't compile
    }
}
Run Code Online (Sandbox Code Playgroud)

解决方案:使用abstract中间层

虽然您不能直接override使用get/ setproperty,但您可以:

  1. 创建一个具有相同名称的new get/ setproperty.

  2. overrideget方法带有新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; }
}
Run Code Online (Sandbox Code Playgroud)

然后,编写无法编译的类.它会编译这个时间,因为你没有真正override"荷兰国际集团的get-only财产; 相反,您将使用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; } }
}
Run Code Online (Sandbox Code Playgroud)

结果:一切正常!

显然,这是按照预期的方式工作的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.
Run Code Online (Sandbox Code Playgroud)

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.
Run Code Online (Sandbox Code Playgroud)

但是,基类定义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.
Run Code Online (Sandbox Code Playgroud)

讨论

此方法允许您将set方法添加到get-only属性.您也可以使用它来执行以下操作:

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

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

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


T.T*_*ler 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;
   }
Run Code Online (Sandbox Code Playgroud)

  • 嗨,超级猫。关于外部可观察的副作用阻止函数成为纯函数,您是正确的,但另一个约束是纯函数结果值必须“仅”依赖于参数。因此,纯静态属性作为函数,必须既不可变又恒定。复杂的编译器可以用第一次调用返回的结果替换所有后续对不带参数的纯函数的调用。 (2认同)

Gis*_*shu -8

因为 Baseclass 的作者已明确声明 Bar 必须是只读属性。推导打破这个契约并使其可读写是没有意义的。

我在这方面支持微软。
假设我是一名新程序员,被告知要针对基类派生进行编码。我编写了一些假设 Bar 无法写入的内容(因为 Baseclass 明确声明它是仅获取属性)。现在根据你的推导,我的代码可能会崩溃。例如

public class BarProvider
{ BaseClass _source;
  Bar _currentBar;

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

  public Bar getBar()
  { return _currentBar;  }
}
Run Code Online (Sandbox Code Playgroud)

由于 Bar 无法按照 BaseClass 接口进行设置,BarProvider 假定缓存是安全的事情 - 因为 Bar 无法修改。但是,如果在派生中可以设置,则如果有人在外部修改 _source 对象的 Bar 属性,则此类可能会提供过时的值。要点是“保持开放,避免做偷偷摸摸的事情和让别人感到惊讶

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

  • 我还是不相信。即使没有显式的 setter,这也不能保证该属性始终返回相同的对象 - 它仍然可以通过其他方法进行更改。 (97认同)
  • 我不同意:基类仅将属性声明为没有设置器;这与只读属性不同。“只读”只是*您*赋予它的含义。C# 中根本没有内置任何相关保证。您可以有一个每次都会更改的仅获取属性,或者一个 get/set 属性,其中 setter 抛出 `InvalidOperationException("This instance is read-only")`。 (52认同)
  • -1 我不同意这个答案,并发现它具有误导性。由于基类定义了任何类都必须遵守的行为(请参阅里氏替换原则),但不(也不应该)限制添加行为。基类只能定义行为必须是什么,并且它不能(也不应该)指定行为“不能是什么”(即“在具体级别上不得可写”)。 (41认同)
  • 那么接口不应该也是如此吗?您可以在接口上拥有只读属性,并在实现类型上拥有读/写属性。 (35认同)
  • 将 getter 和 setter 视为方法,我不明白为什么它比添加新方法更应该是“违反合同”。基类合约与“不”存在的功能无关,只与“存在”的功能相关。 (25认同)
  • 好问题...上面回答这个问题的尝试微弱:) (2认同)
  • @Pauli Østerø:这是怎么回事?从接口/基类的角度来看,方法/setter 是不可见的,因为它不属于那里。另一方面,您应该能够在您的子类上声明其他方法(和设置器),并且有很多非常合理的理由。特别是当 getter 属性被定义为抽象时。 (2认同)
  • 是的 - 如果你想阻止子类添加到合约中(例如添加数据突变行为),你必须简单地密封你的基类。别无退路。(您可以通过将这些字段设置为私有来防止子类改变您的现有字段,但您无法阻止它定义新的行为)。 (2认同)
  • 我不同意这个答案,因为一般来说,界面描述了你可以做什么,而不是你不能做什么(另见 Marcel Valdez 的解释)。例如,当实现仅包含 get 访问器的接口属性时,您可以添加额外的 set 访问器(反之亦然)。奇怪(令人沮丧)的事实是,这不适用于抽象属性和覆盖基本接口属性的子接口。我相信这只是语法的限制。 (2认同)