なぜ新しいキーワードが必要なのですか?また、デフォルトの動作が非表示でオーバーライドされないのはなぜですか?


81

私はこのブログ投稿を見ていて、次の質問がありました。

  • newキーワードが必要なのは、基本クラスのメソッドが非表示になっていることを指定するためだけです。つまり、なぜそれが必要なのですか?overrideキーワードを使わないのなら、基本クラスのメソッドを隠していませんか?
  • C#のデフォルトが非表示でオーバーライドされないのはなぜですか?なぜ設計者はそれをこのように実装したのですか?

6
(私にとって)もう少し興味深い質問は、なぜ非表示が合法であるのかということです。これは通常エラーであり(したがってコンパイラの警告)、めったに必要とされず、常に問題があります。
Konrad Rudolph 2010年

回答:


127

良い質問です。それらを言い換えさせてください。

メソッドを別のメソッドで非表示にすることが合法なのはなぜですか?

その質問に例を挙げて答えさせてください。CLR v1のインターフェイスがあります:

interface IEnumerable
{
    IEnumerator GetEnumerator();
}

素晴らしい。今、CLRにのみ、我々は、私は、この汎用インタフェースただろうV1にジェネリックを持っていたのだ場合。しかし、私はしませんでした。私は今、それに対応して何かを作る必要があり、あなたはジェネリックを持っていて、」男だと思いv2のある一般的なようにIEnumerableを期待するコードとの下位互換性を失うことなく、ジェネリックスのメリットを享受できます。」

interface IEnumerable<T> : IEnumerable
{
    IEnumerator<T> .... uh oh

のGetEnumeratorメソッドを何と呼びIEnumerable<T>ますか?非ジェネリックベースインターフェイスでGetEnumeratorを非表示にする必要があることを忘れないでください。明示的に後方互換の状況にない限り、そのようなものが呼び出されることは決してありません。

それだけでメソッドの非表示が正当化されます。メソッド非表示の正当化に関するその他の考えについては、このテーマに関する私の記事を参照してください

「新しい」なしで非表示にすると警告が発生するのはなぜですか?

あなたが何かを隠していて、それを誤って行っている可能性があることをあなたに知らせたいからです。派生クラスを編集するのではなく、他の誰かが基本クラスを編集したために、誤って何かを隠している可能性があることを忘れないでください。

エラーではなく「新しい」警告なしで非表示になっているのはなぜですか?

同じ理由。基本クラスの新しいバージョンを取得したばかりであるため、誤って何かを隠している可能性があります。これは常に起こります。FooCorpは基本クラスBを作成します。BarCorpはメソッドBarを使用して派生クラスDを作成します。これは、顧客がそのメソッドを気に入っているためです。FooCorpはそれを見て、ちょっといい考えだと言います。その機能を基本クラスに置くことができます。彼らはそうし、Foo.DLLの新しいバージョンを出荷します。そして、BarCorpが新しいバージョンを取得するときに、メソッドが基本クラスのメソッドを非表示にするように言われたらいいのですが。

エラーにすることは、これが脆弱な基本クラスの問題の別の形式であることを意味するため、この状況をエラーではなく警告にする必要があります。C#は、誰かが基本クラスに変更を加えたときに、派生クラスを使用するコードへの影響が最小限に抑えられるように慎重に設計されています。

デフォルトを非表示にしてオーバーライドしないのはなぜですか?

仮想オーバーライドは危険だからです。仮想オーバーライドにより、派生クラスは、基本クラスを使用するようにコンパイルされたコードの動作を変更できます。オーバーライドを行うなどの危険なことを行うことは、偶然ではなく、意識的かつ意図的に行うことです。


1
FWIW、私は「それだけでメソッドの非表示を正当化する」ことに同意しません。下位互換性を壊したほうがよい場合もあります。Python 3は、これが実際に多くの作業を破壊することなく機能することを示しています。「すべてのコストで下位互換性」が唯一の合理的なオプションではないことに注意することが重要です。そして、この理由は別のポイントにも引き継がれます。基本クラスの変更は常に脆弱です。依存ビルドを解除することが実際には望ましい場合があります。(しかし、実際の質問の下にある私のコメントからの質問に答えるために+1。)
Konrad Rudolph

@Konrad:後方互換性が損なわれる可能性があることに同意します。Monoは、将来のリリースで1.1のサポートを終了することを決定しました。
simendsjo 2010年

1
@Konrad:あなたの言語との下位互換性を破ることは、あなたの言語を使用している人々が自分のコードとの下位互換性を簡単に破ることとは大きく異なります。エリックの例は2番目のケースです。そして、そのコードを書いている人は、下位互換性が欲しいという決断をしました。言語は、可能であればその決定をサポートする必要があります。
ブライアン

神の答え、エリック。ただし、デフォルトでオーバーライドするのが悪い理由には、より具体的な理由があります。基本クラスベースの新しい方法ダック()(アヒルオブジェクトを返す)を導入する場合にだけ起こる派生派生クラスで(マリオダックを行う)既存の方法ダックと同じ署名を有することを()(すなわち間で通信あなたとベースの作家)、あなたはないではない、彼らはただダックをしたい時に両方のクラスのクライアントはマリオのアヒルを作りたいです。
jyoungdev 2010年

要約すると、それは主に基本クラスとインターフェースの変更に関係しています。
jyoungdev 2010年

15

派生クラスのメソッドの前にnewキーワードが付いている場合、そのメソッドは基本クラスのメソッドから独立していると定義されます。

ただし、newまたはoverridesを指定しない場合、結果の出力はnewを指定した場合と同じですが、コンパイラの警告が表示されます(基本クラスのメソッドでメソッドを非表示にしていることに気付かない場合があるため、または実際にそれをオーバーライドしたいと思っていて、単にキーワードを含めるのを忘れたかもしれません)。

そのため、間違いを避け、やりたいことを明示的に示すことができ、コードが読みやすくなるため、コードを簡単に理解できます。


12

このコンテキストでの唯一の効果はnew、警告を抑制することであることに注意してください。セマンティクスに変更はありません。

したがって、1つの答えは次のとおりです。非表示new意図的なものであることをコンパイラに通知し、警告を取り除く必要があります。

フォローアップの質問は次のとおりです。メソッドをオーバーライドしない/オーバーライドできない場合、なぜ同じ名前の別のメソッドを導入するのでしょうか。隠蔽は本質的に名前の衝突だからです。そしてもちろん、ほとんどの場合それを避けます。

私が意図的に隠すために考えることができる唯一の正当な理由は、名前がインターフェースによってあなたに強制されるときです。


6

C#では、メンバーはデフォルトで封印されています。つまり、パフォーマンス上の理由から、メンバーをオーバーライドすることはできません(virtualまたはabstractキーワードでマークされていない限り)。新しい修飾子は、明示的に継承されたメンバーを隠すために使用されます。


4
しかし、いつ本当に新しい修飾子を使用したいですか?
simendsjo 2010年

1
基本クラスから継承されたメンバーを明示的に非表示にする場合。継承されたメンバーを非表示にすると、メンバーの派生バージョンが基本クラスバージョンに置き換わります。
ダリンディミトロフ2010年

3
ただし、基本クラスを使用するメソッドは、派生ではなく基本実装を呼び出します。これは私には危険な状況のように思えます。
simendsjo 2010年

@ simendsjo、@ Darin Dimitrov:新しいキーワードが本当に必要なユースケースについても考えるのに苦労しています。常により良い方法があるように私には思えます。また、基本クラスの実装を非表示にすることは、エラーにつながりやすい設計上の欠陥ではないかと思います。
Dirk Vollmar 2010年

2

overrideキーワードを指定せずにオーバーライドがデフォルトであった場合、名前が等しいという理由だけで、誤ってベースのメソッドをオーバーライドする可能性があります。

.Netコンパイラの戦略は、安全のために、問題が発生した場合に警告を発することです。したがって、この場合、オーバーライドがデフォルトの場合、オーバーライドされたメソッドごとに警告が必要になります。「警告:本当に必要かどうかを確認してください。 'をオーバーライドします。


0

私の推測では、主に複数のインターフェイスの継承が原因です。目立たないインターフェイスを使用すると、2つの異なるインターフェイスが同じメソッドシグネチャを使用する可能性が非常に高くなります。newキーワードの使用を許可すると、2つの異なるクラスを作成する代わりに、1つのクラスでこれらの異なる実装を作成できます。

更新...エリックは私にこの例を改善する方法についてのアイデアをくれました。

public interface IAction1
{
    int DoWork();
}
public interface IAction2
{
    string DoWork();
}

public class MyBase : IAction1
{
    public int DoWork() { return 0; }
}
public class MyClass : MyBase, IAction2
{
    public new string DoWork() { return "Hi"; }
}

class Program
{
    static void Main(string[] args)
    {
        var myClass = new MyClass();
        var ret0 = myClass.DoWork(); //Hi
        var ret1 = ((IAction1)myClass).DoWork(); //0
        var ret2 = ((IAction2)myClass).DoWork(); //Hi
        var ret3 = ((MyBase)myClass).DoWork(); //0
        var ret4 = ((MyClass)myClass).DoWork(); //Hi
    }
}

1
複数のインターフェイスを使用する場合、衝突を避けるためにインターフェイスメソッドに明示的に名前を付けることができます。私はあなたが同じシグネチャを持って、両方の「一つのクラスごとに異なる実装」によって何を意味するのか理解していない...
simendsjo

このnewキーワードを使用すると、その時点からすべての子の継承ベースを変更することもできます。
Matthew Whited 2010年

-1

前述のように、メソッド/プロパティの非表示により、他の方法では簡単に変更できなかったメソッドまたはプロパティに関する変更が可能になります。これが役立つ状況の1つは、継承されたクラスが基本クラスで読み取り専用の読み取り/書き込みプロパティを持つことを許可することです。たとえば、基本クラスにValue1-Value40という読み取り専用プロパティが多数あるとします(もちろん、実際のクラスはより適切な名前を使用します)。このクラスの封印された子孫には、基本クラスのオブジェクトを受け取り、そこから値をコピーするコンストラクターがあります。クラスでは、その後の変更は許可されていません。別の継承可能な子孫は、Value1-Value40と呼ばれる読み取り/書き込みプロパティを宣言します。これは、読み取られると基本クラスのバージョンと同じように動作しますが、書き込まれると値を書き込むことができます。

このアプローチの1つの厄介な点は、おそらく誰かが私を助けてくれるかもしれませんが、同じクラス内の特定のプロパティをシャドウイングおよびオーバーライドする方法がわからないことです。CLR言語のいずれかでそれが許可されていますか(私はvb 2005を使用しています)?基本クラスオブジェクトとそのプロパティが抽象であると便利ですが、子孫クラスがそれらをシャドウする前に、中間クラスがValue1からValue40プロパティをオーバーライドする必要があります。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.