关于c#:为什么不可能覆盖仅getter的属性并添加setter?

关于c#:为什么不可能覆盖仅getter的属性并添加setter?

Why is it impossible to override a getter-only property and add a setter?

为什么不允许以下C#代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
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': cannot override because 'BaseClass.Bar' does not have an overridable set accessor


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

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

很明显,getset都是覆盖。基类中没有set,因此编译器会抱怨。就像您无法覆盖基类中未定义的方法一样,您也无法覆盖setter。

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

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

1
2
3
4
5
public int MyProperty
{
    override get { ... }
    set { ... }
}

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

1
public int MyProperty { override get; set; }

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

首先,我想争论一下,拥有仅获取属性并不一定会转换为只读。我将其解释为"可以从此接口/抽象获取此值",这并不意味着该接口/抽象类的某些实现不需要用户/程序显式设置此值。抽象类用于实现部分所需功能的目的。我绝对没有理由说,继承的类不能在不违反任何合同的情况下添加setter。

以下是我今天需要的简化示例。我最终不得不在我的界面中添加一个setter来解决这个问题。添加设置器而不添加例如SetProp方法的原因是,接口的一个特定实现使用DataContract / DataMember进行Prop的序列化,如果我仅出于此目的添加另一个属性,则将不必要地变得复杂序列化。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
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。


这是可能的

tl; dr-如果需要,可以用setter覆盖仅获取方法。基本上就是:

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

  • 如果您什么都不做,那么当通过其基类型调用派生类时,仍将调用旧的get方法。要解决此问题,请在旧的get方法上添加使用overrideabstract中间层,以强制其返回新的get方法的结果。

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

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

    • 如果基本定义是仅get,则可以使用派生性更大的返回类型。

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

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

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

      • 如果将其设为仅get,则可以使用派生较少的返回类型。

    在所有情况下,您都可以根据需要保留相同的返回类型。为了简单起见,以下示例使用相同的返回类型。

    情况:仅存在get的属性

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

    1
    2
    3
    4
    5
    6
    7
    8
    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; } }
    }

    问题:无法使用get / setoverride get

    您想使用get / set属性override,但不会编译。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    public class C : B
    {
        private int _x;
        public override int X
        {
            get { return _x; }
            set { _x = value; }   //  Won't compile
        }
    }

    解决方案:使用abstract中间层

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

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

  • override旧的get方法和新的get方法的访问器,以确保一致性。

  • 因此,首先编写abstract中间层:

    1
    2
    3
    4
    5
    6
    7
    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属性。而是使用new关键字替换它。

    1
    2
    3
    4
    5
    6
    7
    8
    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的预期工作。

    1
    2
    3
    4
    5
    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。但是,它起作用的原因可能不太明显。

    1
    2
    3
    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定义所覆盖,因此它仍然是完全一致的。

    1
    2
    3
    4
    5
    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的属性中。您还可以使用它来执行以下操作:

  • 将任何属性更改为仅get,仅setget -and- set属性,而不管其在基类中是什么。

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

  • 主要的缺点是要做更多的编码,并且在继承树中有额外的abstract class。对于带有参数的构造函数,这可能会有些烦人,因为必须将这些参数复制/粘贴到中间层中。


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

    我怀疑微软有这个局限性,可能是因为引起了同样的误解,或者可能是因为简化了语法。但是,现在可以将范围应用于单独获取或设置,也许我们可以希望重写也可以。

    最高投票答案表明了一个误解,即只读属性应该比读/写属性更"纯",这是荒谬的。只需查看框架中许多常见的只读属性即可;该值不是常数/纯粹是函数;例如,DateTime.Now是只读的,但除纯函数值之外的任何值。尝试"缓存"只读属性的值(假设下次该属性将返回相同的值)是有风险的。

    无论如何,我都使用以下策略之一来克服此限制;两者都不尽如人意,但会让您在这种语言缺陷之外li行:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
       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;
       }

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

    就个人而言,我希望可以取消"属性"的整个概念,除了可以将属性式语法用作调用" get"和" set"方法的语法糖外。这不仅可以方便使用"添加集"选项,还可以允许"获取"返回与"集"不同的类型。尽管这种能力不会经常使用,但是有时让" get"方法返回包装对象,而" set"可以接受包装数据或实际数据有时会很有用。


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

    你不能做这样的事情:

    1
    2
    3
    4
    5
    6
    7
    8
    public class ConcreteClass : BaseClass
    {
        public override int Bar
        {
            get;
            private set;
        }
    }

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


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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    public new int Bar
    {            
        get { return 0; }
        set {}        
    }

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

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

    我在这一点上与Microsoft合作。
    假设我是一位新程序员,被告知要针对Baseclass派生进行编码。我写的东西假定Bar不能被写入(因为Baseclass明确声明它是一个get only属性)。
    现在,根据您的推导,我的代码可能会损坏。例如

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    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不能修改。但是,如果在派生中可以设置set,则如果有人在外部修改_source对象的Bar属性,则此类可以提供陈旧的值。关键是"保持开放,避免做偷偷摸摸的事情和让人惊讶"

    更新:Ilya Ryzhenkov问:"为什么接口不按照相同的规则运行?"
    嗯..我想到这变得更加泥泞。
    接口是一个合同,上面写着"期望实现具有名为Bar的读取属性"。就我个人而言,如果看到接口,则不太可能将其假设为只读。当我在接口上看到get-only属性时,我将其读取为"任何实现都会公开此属性Bar"……在它单击的基类上,因为" Bar是只读属性"。当然,从技术上讲,您并没有违反合同。因此,您在某种意义上是正确的。.最后,我要说:"使误解变得越来越难"。


    为了解决此问题,请使用以下解决方法:

    1
    2
    3
    4
    5
    6
    7
    8
    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);
    }

    这样,我们可以在只读属性上设置对象值。可能无法在所有情况下都起作用!


    解决方案仅适用于一小部分用例,但是:在C#6.0中,将自动为覆盖的仅吸气剂属性添加"只读"设置器。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    public abstract class BaseClass
    {
        public abstract int Bar { get; }
    }

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

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

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

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

    该代码是无用的。仅基类不能告诉您被迫重写Get-Only属性(也许是接口)。基类提供了通用功能,这些功能可能需要实现类的特定输入。因此,通用功能可能会调用抽象属性或方法。在给定的情况下,常见的功能性方法应要求您重写抽象方法,例如:

    1
    public int GetBar(){}

    但是,如果您对此无能为力,并且基类的功能从其自己的公共属性(怪异)中读取,则只需执行以下操作:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    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;
        }
    }

    我想指出一下(奇怪的)评论:我要说的一种最佳实践是,一个类不使用其自己的公共属性,而在它们存在时使用其私有/受保护的字段。所以这是一个更好的模式:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    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。否则,它是无用的,并且您不必在意使用它。 Microsoft不必允许您做您想做的事。原因是:安装员以某种形式存在,并且您可以轻松完成想要的Veerryy。

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

    1
    2
    3
    4
    public abstract class BaseClass
    {
        public int Bar { get; }
    }

    但是实现将具有复杂性的两个方面:

    最少复杂度:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    public abstract class BaseClass
    {
        private int _bar;
        public int Bar {
            get{
                return _bar;
            }}
        public void SetBar(int value) { _bar = value; }
    }

    最复杂:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    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,因为您不希望基类处理它,无权查看它的处理方式,或者被责成真正地快速处理代码违背你的意愿

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

    长篇短篇:Microsoft不需要更改任何内容。您可以选择如何设置实现类,以及在没有构造函数的情况下使用全部或全部不使用基类。


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

    添加二传手会在这里引起尴尬的问题:
    如果将值设置为已存在的单个可能值以外的值,则将发生验证错误。

    但是,规则通常会有例外。例如,在一个派生类中,上下文很有可能将可能的枚举值缩小到十分之三(十分之三),但是此对象的用户仍然需要确定哪个是正确的。派生类需要将确定值的责任委托给此对象的用户。
    重要的是要认识到该对象的用户应充分了解此异常,并承担设置正确值的责任。

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

    这也支持了有效的说法:Gishu提出的"使误解越发严重"。


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

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

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


    因为具有只读属性(没有设置器)的类可能有充分的理由。例如,可能没有任何基础数据存储。允许您创建二传手会破坏班级制定的合同。糟糕的OOP。


    因为那样会破坏封装和实现隐藏的概念。请考虑以下情况:创建类并交付它,然后类的使用者使自己能够设置最初只为其提供吸气剂的属性。它会有效地破坏您可以在实现中依赖的类的所有不变量。


    这并非不可能。您只需在属性中使用" new"关键字。例如,

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    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"会破坏基类实现和子类实现之间的契约。当一个类可以具有多个合同(通过接口或基类)时,这是必需的。

    希望这可以帮助


    推荐阅读

      linux添加用户的命令?

      linux添加用户的命令?,密码,系统,软件,联系方式,用户,新增,信息,管理人员,

      linux添加网口命令?

      linux添加网口命令?,网络,系统,工具,信息,最新,设备,地址,工作,密码,数据,lin

      linux命令添加挂载点?

      linux命令添加挂载点?,系统,信息,名称,情况,电脑,名字,设备,磁盘,分区,命令,l

      linux添加端口命令?

      linux添加端口命令?,服务,系统,工具,端口,位置,情况,软件,对外开放,命令,文

      linux命令添加ip?

      linux命令添加ip?,地址,系统,代码,网络,服务,密码,命令,百度,工具,设备,Linux

      linux命令中添加用户?

      linux命令中添加用户?,系统,密码,软件,用户,命令,信息,目录,用户名,账号,文

      linux添加分辨率命令?

      linux添加分辨率命令?,系统,情况,分辨率,状态,屏幕分辨率,屏幕,命令,桌面,

      linux系统命令添加?

      linux系统命令添加?,系统,密码,代码,简介,命令,实战,项目,工作,资料,基础,lin

      linux命令添加磁盘?

      linux命令添加磁盘?,系统,管理,信息,情况,电脑,数据,磁盘,命令,环境,工作,Lin

      添加网卡linux的命令?

      添加网卡linux的命令?,地址,系统,网络,信息,设备,电脑,网卡,发行,基本知识,

      linux新硬件添加命令?

      linux新硬件添加命令?,系统,设备,数据,业务,服务,盘前,信息,密码,名字,硬盘,

      添加网卡linux的命令?

      添加网卡linux的命令?,地址,系统,网络,信息,设备,电脑,网卡,发行,基本知识,

      linux新硬件添加命令?

      linux新硬件添加命令?,系统,设备,数据,业务,服务,盘前,信息,密码,名字,硬盘,

      linux命令添加一个组?

      linux命令添加一个组?,密码,系统,管理,代码,用户组,用户,命令,新增,文件,目

      linux添加快捷命令?

      linux添加快捷命令?,软件,名称,较大,第一,发行,命令,终端,快捷键,窗口,方法,

      linux命令添加文件?

      linux命令添加文件?,工作,简介,数据,系统,文件,命令,操作,文件名,内容,终端,l

      linux添加自启命令?

      linux添加自启命令?,服务,状态,密码,系统,脚本,第三,人员,标准,在线,实时,Pyt

      linux自启命令添加?

      linux自启命令添加?,服务,系统,工具,第三,第一,代码,密码,标准,项目,实时,lin

      linux命令怎么添加组?

      linux命令怎么添加组?,系统,密码,管理,用户,电脑,代码,新增,用户组,命令,账

      linux添加账户的命令?

      linux添加账户的命令?,密码,系统,信息,用户,软件,地址,状态,数字,命令,用户