web-dev-qa-db-ja.com

なぜゲッターのみのプロパティをオーバーライドしてセッターを追加できないのですか?

次の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'にはオーバーライド可能なセットアクセサーがないため、オーバーライドできません

138
ripper234

Baseclassのライターは、Barは読み取り専用プロパティである必要があることを明示的に宣言しているためです。派生物がこのコントラクトを破り、それを読み書き可能にすることは意味がありません。

これについてはマイクロソフトと一緒です。
ベースクラスの派生に反してコーディングするように言われた新しいプログラマーだとしましょう。私は、Barに書き込むことができないことを前提とする何かを書きます(Baseclassがget onlyプロパティであると明示的に述べているため)。今、あなたの派生で、私のコードが壊れる可能性があります。例えば.

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は読み取り専用プロパティです」とクリックするベースクラスで。もちろん、技術的には契約を破るわけではありません。だからあなたはある意味で正しい..私は「誤解が生じるのを可能な限り難しくする」と言って終わります。

1
Gishu

主な理由は、単純に構文があまりにも明示的であり、他の方法では機能しないためだと思います。このコード:

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

getsetの両方がオーバーライドであることは明確です。基本クラスにはsetがないため、コンパイラーは文句を言います。基本クラスで定義されていないメソッドをオーバーライドできないように、セッターもオーバーライドできません。

コンパイラは意図を推測し、オーバーライドできるメソッド(この場合はゲッター)にのみオーバーライドを適用する必要があると言うかもしれませんが、これはC#の設計原則の1つに反します-コンパイラは意図を推測してはいけません、知らないうちに間違って推測される可能性があるためです。

次の構文はうまくいくと思いますが、Eric Lippertが言い続けているように、このような小さな機能を実装することは依然として大きな労力です...

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

または、自動実装プロパティの場合、

public int MyProperty { override get; set; }
36
Roman Starkov

今日、私はまったく同じ問題に出くわしましたが、これを望む非常に正当な理由があると思います。

最初に、get-onlyプロパティを持つことが必ずしも読み取り専用に変換されるとは限らないことを主張したいと思います。私はそれを「このインターフェース/抽象からこの値を取得できる」と解釈しますが、それはそのインターフェース/抽象クラスの実装がこの値を明示的に設定するためにユーザー/プログラムを必要としないという意味ではありません。抽象クラスは、必要な機能の一部を実装する目的に役立ちます。継承されたクラスが契約に違反せずにセッターを追加できなかった理由はまったくありません。

以下は、私が今日必要としていたものの簡単な例です。これを回避するために、インターフェイスにセッターを追加する必要がありました。 SetPropメソッドを追加せずにセッターを追加する理由は、インターフェイスの特定の実装が、Propのシリアル化にDataContract/DataMemberを使用したためです。これは、目的のためだけに別のプロパティを追加する必要がある場合、不必要に複雑になりますシリアル化の。

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を使用することについての言及はここでは当てはまりません。単に機能しないだけでなく、必要な結果、つまりインターフェイスで記述された仮想ゲッターが得られない場合もあります。

19
JJJ

それが可能だ

tl; dr-必要に応じて、セッターでget-onlyメソッドをオーバーライドできます。基本的には次のとおりです。

  1. 同じ名前を使用して、newgetの両方を持つsetプロパティを作成します。

  2. 他に何もしない場合、派生クラスがその基本型を介して呼び出されたときに、古いgetメソッドが引き続き呼び出されます。これを修正するには、古いabstractメソッドでoverrideを使用するget中間層を追加して、新しいgetメソッドの結果を強制的に返します。

これにより、基本定義にプロパティがない場合でも、get/setでプロパティをオーバーライドできます。

ボーナスとして、必要に応じて戻りタイプを変更することもできます。

  • 基本定義がget- onlyの場合、より派生した戻り値型を使用できます。

  • 基本定義がset- onlyの場合、派生の少ない戻り値型を使用できます。

  • 基本定義が既にget/setであった場合、次のようになります。

    • より派生した戻り値型を使用できますifset- only;

    • 派生の少ない戻り型を使用できますifget- onlyにします。

すべての場合において、必要に応じて同じ戻り値の型を保持できます。以下の例では、簡単にするために同じ戻り値の型を使用しています。

状況:既存のget- onlyプロパティ

変更できないクラス構造があります。たぶんそれはただ一つのクラスであるか、それは既存の継承ツリーです。いずれにしても、プロパティに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中間層を使用します

override/getプロパティでsetを直接使用することはできませんが、can

  1. newget/setプロパティを同じ名前で作成します。

  2. override古い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- onlyプロパティを実際にget 'ingしていないため、今回はコンパイルします。代わりに、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をその基底クラスの1つとして表示する場合、すべてが意図したとおりに機能します。 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.

討論

このメソッドを使用すると、setメソッドをgetのみのプロパティに追加できます。次のようなことを行うためにも使用できます。

  1. 基本クラスにあるものに関係なく、プロパティをget- only、set- only、またはget- and -setプロパティに変更します。 。

  2. 派生クラスのメソッドの戻り値の型を変更します。

主な欠点は、行うべきコーディングが多くなり、abstract class継承ツリー内。これらは中間層にコピー/貼り付けする必要があるため、パラメーターを受け取るコンストラクターでは少し面倒です。

14
Nat

派生型でゲッターをオーバーライドできないことはアンチパターンであることに同意します。読み取り専用は、実装の欠如を指定するものであり、純粋な機能のコントラクトではありません(トップ投票の回答によって暗示されます)。

同じ誤解が促進されたためか、おそらく文法を簡素化したために、Microsoftにこの制限があったと思われます。ただし、スコープを個別に取得または設定するために適用できるようになったため、オーバーライドも可能になると期待できます。

読み取り専用プロパティは、読み取り/書き込みプロパティよりも「純粋」でなければならないという、トップ投票の回答で示された誤解はばかげています。フレームワークの多くの一般的な読み取り専用プロパティを見てください。値は定数ではなく、純粋に機能的です。たとえば、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;
   }
9
T.Tobler

問題は、何らかの理由でMicrosoftが3つの異なるタイプのプロパティがあるべきだと決定したことです。読み取り専用、書き込み専用、および読み取りと書き込みのいずれかです。プロパティは、同一に宣言されたプロパティによってのみオーバーライドできます。希望することを行うには、同じ名前と署名を持つ2つのプロパティを作成する必要があります。1つは読み取り専用で、もう1つは読み取り/書き込みです。

個人的には、プロパティのような構文を「get」および「set」メソッドを呼び出すための構文シュガーとして使用できることを除いて、「プロパティ」の概念全体を廃止できることを望みます。これにより、「セットの追加」オプションが容易になるだけでなく、「get」が「set」とは異なるタイプを返すこともできます。このような機能はそれほど頻繁に使用されることはありませんが、「set」がラッパーまたは実際のデータを受け入れる一方で、「get」メソッドがラッパーオブジェクトを返すと便利な場合があります。

1
supercat

私はあなたのすべてのポイントを理解できますが、事実上、C#3.0の自動プロパティはその場合役に立たなくなります。

そのようなことはできません:

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

IMO、C#はそのようなシナリオを制限すべきではありません。それを適切に使用するのは開発者の責任です。

1
Thomas Danecker

おそらく、新しいプロパティを作成することで問題を回避できます。

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

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

ユースケースのごく一部のソリューションのみですが、それでもC#6.0では、オーバーライドされたゲッター専用プロパティに「読み取り専用」セッターが自動的に追加されます。

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

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

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

質問のタイトルを変更して、質問が単に抽象プロパティのオーバーライドに関するものであるか、または一般的にクラスの取得専用プロパティのオーバーライドに関するものであるかのいずれかに詳述する必要があります。


前者の場合(抽象プロパティのオーバーライド)

そのコードは役に立たない。基本クラスだけでは、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プロパティをオーバーライドする)

すべての非抽象プロパティにはセッターがあります。それ以外の場合は役に立たないので、使用する必要はありません。マイクロソフトは、あなたが望むことをすることを許可する必要はありません。理由:セッターは何らかの形で存在し、望むものを簡単に達成できますVeerryy

基本クラス、または{get;}を使用してプロパティを読み取ることができるクラスには、[〜#〜] some [〜#〜]がありますそのプロパティの公開セッターの種類。メタデータは次のようになります。

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をオーバーライドしたい場合があります。ベースクラスでそれを処理したくない、処理方法を確認するためのアクセス権がない、またはコードを実際に高速化するタスクを課されたからです'あなたの意志に反する汚い。

後者と前者の両方で(結論)

長文短:Microsoftが何かを変更する必要はありません。実装クラスの設定方法を選択し、コンストラクターを使用せずに、基本クラスをすべて使用するか、まったく使用しないかを選択できます。

0
Suamere

Reflectionを使用してこれを実現するための回避策を次に示します。

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

このようにして、読み取り専用のプロパティにオブジェクト値を設定できます。ただし、すべてのシナリオで機能するとは限りません。

0
user2514880