ゲッターのみのプロパティをオーバーライドしてセッターを追加することができないのはなぜですか?[閉まっている]
-
01-07-2019 - |
質問
次の 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」にはオーバーライド可能なセット アクセサーがないため、オーバーライドできません
解決
なぜなら、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 プロパティを外部で変更した場合、このクラスは古い値を提供する可能性があります。ポイントは「オープンであり、卑劣なことをしたり、人々を驚かせたりすることは避けてください'
アップデート: イリヤ・リジェンコフは、「では、なぜインターフェイスは同じルールに従って動作しないのでしょうか?」と尋ねます。ふーむ..考えれば考えるほど濁ってきます。
インターフェイスは、「実装には Bar という名前の読み取りプロパティがあることを期待する」という契約です。 個人的には インターフェイスを見た場合、読み取り専用であるという仮定を立てる可能性ははるかに低くなります。インターフェイスで取得専用プロパティを見たとき、「どの実装でもこの属性 Bar が公開されるだろう」と解釈します。基本クラスでは、「Bar は読み取り専用プロパティ」としてクリックされます。もちろん厳密に言えば契約違反にはなりませんが…あなたはもっとやっているのです。つまり、あなたはある意味では正しいのです。最後に「誤解が生じにくいようにする」と言って終わりたいと思います。
他のヒント
主な理由は単純に、構文が明示的すぎて他の方法では機能しないことだと思います。このコード:
public override int MyProperty { get { ... } set { ... } }
両方とも非常に明白です get
そしてその set
はオーバーライドです。ありません set
基本クラスにあるため、コンパイラはエラーを出します。基本クラスで定義されていないメソッドをオーバーライドできないのと同様に、セッターもオーバーライドできません。
コンパイラはユーザーの意図を推測し、オーバーライドできるメソッド (つまり、オーバーライド) にのみオーバーライドを適用する必要があると言う人もいるかもしれません。しかし、これは C# の設計原則の 1 つに反します。つまり、コンパイラはユーザーの意図を推測してはなりません。ユーザーが知らないうちに間違った推測をする可能性があるからです。
次の構文はうまくいくかもしれないと思いますが、Eric Lippert が言い続けているように、このような小さな機能を実装するのは依然として多大な労力です...
public int MyProperty
{
override get { ... }
set { ... }
}
または、自動実装プロパティの場合は、
public int MyProperty { override get; set; }
私は今日、まったく同じ問題に遭遇しました。これを望むのには非常に正当な理由があると思います。
まず最初に、取得専用プロパティがあることが必ずしも読み取り専用になるわけではない、ということを主張したいと思います。私はこれを「このインターフェイス/抽象からこの値を取得できる」と解釈しますが、そのインターフェイス/抽象クラスの一部の実装では、ユーザー/プログラムがこの値を明示的に設定する必要がないという意味ではありません。抽象クラスは、必要な機能の一部を実装するという目的を果たします。継承クラスがコントラクトに違反せずにセッターを追加できない理由はまったくわかりません。
以下は、今日必要なものの簡略化された例です。これを回避するために、インターフェイスにセッターを追加する必要がありました。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 を使用することについての言及はここでは適用されません。単に機能しません。機能したとしても、必要な結果、つまりインターフェイスで説明されている仮想ゲッターは得られません。
それが可能だ
先生- 必要に応じて、取得専用メソッドをセッターでオーバーライドできます。基本的には次のとおりです。
を作成します
new
両方を持つプロパティget
そしてset
同じ名前を使用しています。他に何もしなければ、古いままになります。
get
派生クラスがその基本型を通じて呼び出された場合でも、メソッドは引き続き呼び出されます。これを修正するには、abstract
を使用する中間層override
古いものでget
新しいものを強制的に返すメソッドget
メソッドの結果。
これにより、プロパティをオーバーライドできるようになります。 get
/set
たとえその基本定義に 1 つが欠けていたとしても。
さらに、必要に応じて戻り値の型を変更することもできます。
基本定義が
get
-only の場合、より派生した戻り値の型を使用できます。基本定義が
set
-only の場合、より派生の少ない戻り値の型を使用できます。基本定義がすでに存在する場合
get
/set
, 、 それから:より派生した戻り値の型を使用できます もし あなたはそれを成し遂げます
set
-のみ;あまり派生されていない戻り値の型を使用できます もし あなたはそれを成し遂げます
get
-のみ。
必要に応じて、どのような場合でも、同じ戻り値の型を維持できます。以下の例では、簡単にするために同じ戻り値の型を使用しています。
状況:既存の get
-only プロパティ
変更できないクラス構造があります。おそらくそれは 1 つのクラスであるか、既存の継承ツリーである可能性があります。いずれにせよ、追加したいのは、 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
とともに get
/set
財産、あなた できる:
を作成します
new
get
/set
同じ名前のプロパティ。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
をしている 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; } }
}
結果:すべてがうまくいきます!
明らかに、これは意図したとおりに機能します 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
- のみのプロパティ。次のようなことにも使用できます。
任意のプロパティを に変更します。
get
-のみ、set
-のみ、またはget
-そして-set
基本クラスのプロパティに関係なく、プロパティ。派生クラスのメソッドの戻り値の型を変更します。
主な欠点は、行うべきコーディングが増えることと、余分な作業が必要なことです。 abstract class
継承ツリー内。パラメーターを受け取るコンストラクターでは、パラメーターを中間層でコピー/ペーストする必要があるため、これは少し面倒になる可能性があります。
派生型でゲッターをオーバーライドできないのはアンチパターンであることに同意します。読み取り専用は、純粋な関数のコントラクトではなく、実装の欠如を指定します (トップ投票の回答が暗示しています)。
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;
}
新しいプロパティを作成することで問題を回避できるかもしれません。
public new int Bar
{
get { return 0; }
set {}
}
int IBase.Bar {
get { return Bar; }
}
あなたの言いたいことはすべて理解できますが、その場合、事実上、C# 3.0 の自動プロパティは役に立たなくなります。
そのようなことはできません:
public class ConcreteClass : BaseClass
{
public override int Bar
{
get;
private set;
}
}
IMO、C# はそのようなシナリオを制限すべきではありません。それに応じて使用するのは開発者の責任です。
問題は、何らかの理由で Microsoft が、次の 3 種類のプロパティが存在する必要があると決定したことです。読み取り専用、書き込み専用、および読み取り/書き込み。指定されたコンテキスト内で指定された署名を持つもののうち 1 つだけが存在できます。プロパティは、同じように宣言されたプロパティによってのみオーバーライドできます。希望することを行うには、同じ名前と署名を持つ 2 つのプロパティを作成する必要があります。そのうちの 1 つは読み取り専用で、もう 1 つは読み取り/書き込み可能です。
個人的には、「get」メソッドと「set」メソッドを呼び出すための糖鎖構文としてプロパティ風の構文を使用できることを除いて、「プロパティ」という概念全体が廃止されることを望みます。これにより、「add set」オプションが容易になるだけでなく、「get」が「set」とは異なる型を返すことも可能になります。このような機能はそれほど頻繁には使用されませんが、「set」がラッパーまたは実際のデータを受け入れることができる一方で、「get」メソッドがラッパー オブジェクトを返すようにすると便利な場合があります。
リフレクションを使用してこれを実現するための回避策は次のとおりです。
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);
}
このようにして、読み取り専用のプロパティにオブジェクト値を設定できます。ただし、すべてのシナリオで機能するとは限りません。
質問のタイトルを変更して、質問が抽象プロパティのオーバーライドのみに関するものであること、または質問がクラスの 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; }
}
後者の場合 (クラスの取得専用プロパティをオーバーライドします)
すべての非抽象プロパティにはセッターがあります。それ以外の場合は役に立たないので、使用する必要はありません。Microsoft は、ユーザーがやりたいことを許可する必要はありません。理由は次のとおりです。セッターは何らかの形で存在し、必要なことを実現できます ヴェリー 簡単に。
基本クラス、またはプロパティを読み取ることができる任意のクラス {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
それは、基本クラスにそれを処理させたくない、その処理方法をレビューするアクセス権がない、またはあなたの意志に反して、本当に手早く汚いコードを改ざんする任務を与えられたからです。
後者でも前者でも (結論)
短編小説:Microsoft が何かを変更する必要はありません。実装クラスの設定方法を選択でき、コンストラクターを除いて、基本クラスをすべて使用することも、まったく使用しないこともできます。
ユースケースのごく一部のみを対象としたソリューションですが、それでも次のとおりです。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;
}
}
IL レベルでは、読み取り/書き込みプロパティは 2 つのメソッド (ゲッターとセッター) に変換されるためです。
オーバーライドする場合は、基礎となるインターフェイスをサポートし続ける必要があります。セッターを追加できれば、事実上、クラスのインターフェイスに関する限り、外部からは見えない新しいメソッドを追加することになります。
確かに、新しいメソッドを追加しても互換性自体が損なわれるわけではありませんが、メソッドは隠されたままになるため、これを禁止するという決定は完全に理にかなっています。
それはカプセル化と実装の隠蔽の概念を壊すことになるからです。クラスを作成して配布し、その後、クラスのコンシューマーが、元々ゲッターのみを提供していたプロパティを自分で設定できるようにする場合を考えてみましょう。これは、実装内で依存できるクラスの不変条件を効果的に破壊することになります。
読み取り専用プロパティ (セッターなし) を持つクラスには、おそらく十分な理由があるからです。たとえば、基盤となるデータストアが存在しない可能性があります。セッターの作成を許可すると、クラスによって定められた契約が破棄されます。それはただ悪いOOPです。
基本クラスの読み取り専用プロパティは、このプロパティがクラス内から常に決定できる値 (たとえば、オブジェクトの (db-) コンテキストに一致する列挙値) を表すことを示します。したがって、値を決定する責任はクラス内にあります。
セッターを追加すると、ここで厄介な問題が発生します。すでに存在している単一の値以外の値を設定すると、検証エラーが発生します。
ただし、ルールには例外が存在することがよくあります。たとえば、ある派生クラスでは、コンテキストによって可能な enum 値が 10 個のうち 3 個に絞り込まれている可能性が非常に高くなりますが、それでもこのオブジェクトのユーザーはどれが正しいかを判断する必要があります。派生クラスは、値を決定する責任をこのオブジェクトのユーザーに委任する必要があります。認識することが重要 それはこのオブジェクトのユーザーです この例外については十分に認識しておく必要があります そして正しい値を設定する責任を負います。
このような状況における私の解決策は、プロパティを読み取り専用のままにし、例外をサポートするために新しい読み取り/書き込みプロパティを派生クラスに追加することです。元のプロパティをオーバーライドすると、単に新しいプロパティの値が返されます。新しいプロパティには、この例外のコンテキストを適切に示す適切な名前を付けることができます。
これは次のような有効な発言も裏付けています。「できるだけ誤解が生じないように」 by 岐州
これは不可能ではありません。プロパティで「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」を使用すると、基本クラスの実装とサブクラスの実装の間の契約が破られます。これは、クラスが複数のコントラクト (インターフェイスまたは基本クラスのいずれかを介して) を持つことができる場合に必要です。
お役に立てれば