Javaとは異なり、「new」および「virtual + override」キーワードでC#が作成されたのはなぜですか?


61

Javaではノーがあるvirtualnewoverrideメソッド定義のキーワード。したがって、メソッドの動作は理解しやすいです。場合原因DerivedClassが延びBaseClassのを、同じ名前と同じシグネチャを持つ方法有するBaseClassのその後ランタイム多形で行われるオーバーライドが(メソッドではない提供しますstatic)。

BaseClass bcdc = new DerivedClass(); 
bcdc.doSomething() // will invoke DerivedClass's doSomething method.

C#に来ると、非常に多くの混乱が発生し、newor virtual+deriveまたはnew + virtualオーバーライドがどのように機能するかを理解するのが難しくなります

なぜ世界DerivedClassで同じ名前と同じシグネチャを持つメソッドを追加しBaseClass、新しい動作を定義するのかを理解することはできませんが、実行時のポリモーフィズムでは、BaseClassメソッドが呼び出されます!(これはオーバーライドではありませんが、論理的にはそうする必要があります)。

以下の場合にはvirtual + override論理的な実装が正しいですが、プログラマが、彼はコーディングの際に上書きするようにユーザーに権限を与えるべき方法だと思うしなければならないのに。これには賛否両論があります(今は行かないでください)。

それで、なぜC#には論理的な推論と混乱のためのスペースがあるのか。それで、どの現実世界の文脈で、代わりに使用するか、またvirtual + override代わりに使用することを考えるべきかという質問を再構成できますか?newnewvirtual + override


特にOmarからの非常に良い回答の後に、C#デザイナーはメソッドを作成する前にプログラマが考える必要があることについてより多くのストレスを与えたと思います。

今、私は質問を念頭に置いています。Javaのように、次のようなコードがあった場合

Vehicle vehicle = new Car();
vehicle.accelerate();

後で新しいクラスをSpaceShip派生させVehicleます。次にcar、すべてをSpaceShipオブジェクトに変更したいので、1行のコードを変更するだけです

Vehicle vehicle = new SpaceShip();
vehicle.accelerate();

これは、コードのどのポイントでも私のロジックを壊しません。

しかし、C#の場合、if SpaceShipVehicleクラスをオーバーライドせずにaccelerate使用するnewと、コードのロジックが壊れます。それは不利ではないですか?


77
Javaのやり方に慣れているだけで、C#キーワードを独自の用語で理解するのに時間をかけていません。私はC#で作業し、用語をすぐに理解し、Javaの奇妙な方法を見つけました。
ロバートハーヴェイ14年

12
IIRC、C#はこれを「明快さを優先する」ために行いました。明示的に「新規」または「オーバーライド」と言う必要がある場合、メソッドがベースクラスの動作をオーバーライドしているかどうかを把握しようとするのではなく、何が起こっているのかを明確かつ即座に明らかにします。また、どのメソッドを仮想として指定し、どのメソッドを指定しないかを指定できると非常に便利です。(Javaはこれをfinal;で行いますが、逆の方法です)。
ロバートハーヴェイ14年

15
「Javaには、メソッド定義のための仮想の新しいオーバーライドキーワードはありません。」を除き@Overrideます。
svick 14年


3
@svickアノテーションとキーワードは同じものではありません:)
Anirban Nag 'tintinmj' 14年

回答:


86

C#がこのようにした理由を尋ねたので、C#の作成者に尋ねることをお勧めします。C#の主任アーキテクトであるAnders Hejlsbergは、インタビューの中で(Javaのように)デフォルトでvirtualを使用しないことを選択した理由に答えました。関連するスニペットは以下のとおりです。

Javaにはデフォルトで、メソッドを非仮想としてマークするfinalキーワードを使用して仮想があります。まだ2つの概念を学ぶ必要がありますが、多くの人は最終的なキーワードを知らないか、積極的に使用しません。C#は、仮想および新規/オーバーライドを使用して、これらの決定を意識的に行うことを強制します。

いくつかの理由があります。1つはパフォーマンスです。Javaでコードを書くと、メソッドをfinalにマークするのを忘れることがわかります。したがって、これらのメソッドは仮想です。仮想であるため、パフォーマンスも低下します。仮想メソッドであることに関連するパフォーマンスのオーバーヘッドがあります。それが一つの問題です。

より重要な問題はバージョン管理です。仮想メソッドについては2つの考え方があります。アカデミックな学派は、「いつかそれをオーバーライドしたいかもしれないので、すべてが仮想であるべきだ」と言っています。現実の世界で実行される実際のアプリケーションを構築することから生まれる実用的な考え方は、「仮想化するものには本当に注意を払わなければならない」と述べています。

プラットフォームで何かを仮想化すると、将来どのように進化するかについて非常に多くの約束をしています。非仮想メソッドの場合、このメソッドを呼び出すとxとyが発生することをお約束します。APIで仮想メソッドを公開するとき、このメソッドを呼び出すとxとyが発生することを約束するだけではありません。また、このメソッドをオーバーライドするとき、これらの他のメソッドに関してこの特定のシーケンスで呼び出すことを約束し、状態はthisおよびthat不変になります。

APIで仮想と言うたびに、コールバックフックを作成しています。OSまたはAPIフレームワークのデザイナーとして、あなたはそれについて本当に注意しなければなりません。APIの任意のポイントでユーザーがオーバーライドおよびフックすることは望ましくありません。これらの約束を必ずしも果たすことができないためです。そして、人々は何かを仮想化するとき、彼らがしている約束を完全に理解していないかもしれません。

インタビューでは、開発者がクラスの継承設計についてどのように考え、それがどのように決定につながったのかについて、さらに議論しています。

次に、次の質問に答えます。

BaseClassと同じ名前と同じシグネチャでDerivedClassにメソッドを追加し、新しい動作を定義する理由を世界で理解することはできませんが、ランタイムポリモーフィズムでは、BaseClassメソッドが呼び出されます!(これはオーバーライドではありませんが、論理的にはそうする必要があります)。

これは、派生クラスが基本クラスのコントラクトに従わないが、同じ名前のメソッドを持っていることを宣言したい場合です。(C#newoverrideC#の違いを知らない人は、このMSDNページを参照してください)。

非常に実用的なシナリオは次のとおりです。

  • というクラスを持つAPIを作成しましたVehicle
  • 私はあなたのAPIの使用を開始し、派生しましたVehicle
  • あなたのVehicleクラスにはメソッドがありませんでしたPerformEngineCheck()
  • 私にはCar、クラス、私はメソッドを追加しますPerformEngineCheck()
  • APIの新しいバージョンをリリースし、を追加しましたPerformEngineCheck()
  • クライアントがAPIに依存しているため、メソッドの名前を変更できません。
  • したがって、新しいAPIに対して再コンパイルすると、C#はこの問題について警告します。たとえば、

    ベースPerformEngineCheck()がそうでない場合virtual

    app2.cs(15,17): warning CS0108: 'Car.PerformEngineCheck()' hides inherited member 'Vehicle.PerformEngineCheck()'.
    Use the new keyword if hiding was intended.

    ベースPerformEngineCheck()virtual次の場合:

    app2.cs(15,17): warning CS0114: 'Car.PerformEngineCheck()' hides inherited member 'Vehicle.PerformEngineCheck()'.
    To make the current member override that implementation, add the override keyword. Otherwise add the new keyword.
  • ここで、クラスが実際に基本クラスのコントラクトを拡張しているのか、それとも別のコントラクトであるのに同じ名前であるのかを明示的に決定する必要があります。

  • 作成することによりnew、基本メソッドの機能が派生メソッドと異なっていても、クライアントを壊しません。参照したコードVehicleCar.PerformEngineCheck()呼び出されたとは表示されませんが、参照があったコードには、でCar提供した同じ機能が引き続き表示されPerformEngineCheck()ます。

同様の例は、基本クラスの別のメソッドがPerformEngineCheck()(特に新しいバージョンで)呼び出している場合PerformEngineCheck()、派生クラスの呼び出しをどのように防ぐのですか?Javaでは、その決定は基本クラスにかかっていますが、派生クラスについては何も知りません。C#で、その決定が載置両方(を介してベースクラスのvirtualキーワード)、および(介して派生クラスにnew及びoverrideキーワード)。

もちろん、コンパイラーがスローするエラーは、プログラマーが予期せずエラーを起こさないようにするための有用なツールも提供します(つまり、オーバーライドするか、気付かないで新しい機能を提供します)。

アンダースが言ったように、現実世界は私たちをそのような問題に追い込みます。もしゼロから​​始めるなら、私たちは決して入りたくないでしょう。

編集:newインターフェイスの互換性を確保するために使用する必要がある場所の例を追加しました。

編集:コメントを読んでいるときに、他のシナリオ例(Brianが言及)についてEric Lippert(当時はC#設計委員会のメンバーの1人)による記事にも出会いました。


パート2:更新された質問に基づく

しかし、C#の場合、SpaceShipがVehicleクラスのAccelerateをオーバーライドせず、newを使用すると、コードのロジックが壊れます。それはデメリットではありませんか?

SpaceShip実際にオーバーライドするのか、Vehicle.accelerate()それとも異なるのかは誰が決めるのですか?SpaceShip開発者でなければなりません。SpaceShip開発者が基本クラスのコントラクトを保持していないと判断した場合、への呼び出しVehicle.accelerate()はに移動するべきではありませんSpaceShip.accelerate()か?それは彼らがそれをマークするときnewです。ただし、契約が実際に維持されると判断した場合は、実際にマークしoverrideます。どちらの場合でも、契約に基づいて正しいメソッドを呼び出すことにより、コードは正しく動作します。コードSpaceShip.accelerate()は、実際にオーバーライドするのかVehicle.accelerate()、名前の衝突であるのかをどのように判断できますか?(上記の例を参照してください)。

ただし、暗黙的な継承の場合SpaceShip.accelerate()、のコントラクトを保持していなくてもVehicle.accelerate()、メソッド呼び出しはに進みSpaceShip.accelerate()ます。


12
パフォーマンスポイントは今では完全に廃止されています。証明のために、最終ではないがオーバーロードされていないメソッドを介してフィールドにアクセスするには1サイクルかかることを示す私のベンチマークを参照してください。
maaartinus 14年

7
確かにそうかもしれません。問題は、C#がそうすることを決めたときに、なぜその時点でそれをしたのかということでした。したがって、この答えは有効です。質問がそれがまだ理にかなっているかどうかである場合、それは別の議論、私見です。
オメルイクバル14年

1
私はあなたに完全に同意します。
maaartinus 14年

2
私見では、機能を非仮想にするための使用法は間違いなくありますが、ベースクラスのメソッドをオーバーライドしたり、インターフェースを実装したりすることが予期されない何かがそうなると、予期しない落とし穴のリスクが生じます。
supercat

3
@Nilzor:したがって、アカデミック対実用的に関する議論。実際には、何かを壊す責任は最後のチェンジメーカーにあります。基本クラスを変更し、既存の派生クラスが変更しないことに依存している場合、それは問題ではありません(「それら」はもはや存在しない可能性があります)。そのため、そのクラスが仮想クラスであってはならない場合でも、基本クラスは派生クラスの動作にロックされます。そしてあなたが言うように、RhinoMockが存在します。はい、すべてのメソッドを正しく完成させると、すべてが同等になります。ここでは、これまでに構築されたすべてのJavaシステムを示します。
deworde

94

それが正しいことだからです。実際、すべてのメソッドをオーバーライドできるようにすることは間違っています。基本クラスへの変更がサブクラスを破壊するかどうかを判断する方法がない、脆弱な基本クラスの問題につながります。したがって、オーバーライドしないメソッドをブラックリストに載せるか、オーバーライドが許可されているメソッドをホワイトリストに入れる必要があります。2つのうち、ホワイトリストは(誤って壊れやすい基本クラスを作成できないため)安全であるだけでなく、構成を優先して継承を回避する必要があるため、必要な作業も少なくなります。


7
任意のメソッドのオーバーライドを許可するのは間違っています。スーパークラスの設計者がオーバーライドしても安全であると指定したメソッドのみをオーバーライドできます。
ドーバル14年

21
Eric Lippertは、彼の投稿であるVirtual Methods and Brittle Base Classesでこれについて詳しく説明しています。
ブライアン

12
Javaにはfinal修飾子がありますが、問題は何ですか?
セージボルシュ14

13
@corsiKa「オブジェクトは拡張に対してオープンであるべきです」-なぜですか?基本クラスの開発者が継承について考えたことがない場合、バグにつながるコードに微妙な依存関係と仮定があることがほぼ保証されHashMapます(覚えていますか?)。継承のために設計し、契約を明確にするか、または誰もクラスを継承できないようにします。@Overrideそれまでに振る舞いを変更するには遅すぎたため、絆創膏として追加されましたが、元のJava開発者(特にJosh Bloch)でさえ、これが悪い決定であることに同意します。
Voo 14年

9
@jwenting「オピニオン」は面白い言葉です。人々はそれを事実に添付して、それらを無視できるようにしています。しかし、それが価値があるものは何でも、ジョシュア・ブロックの「意見」(効果的なJava項目17を参照)、ジェームズ・ゴスリングの「意見」(このインタビューを参照)、そしてオマー・イクバルの答えで指摘されているように、アンデルス・ヘイルスバーグの「意見」でもあります。(そして、彼はオプトインとオプトアウトの両方に取り組んだことはありませんが、エリック・リッパートは、継承も危険であることを明確に同意しています。)
ドーバル14年

33

ロバート・ハーヴェイが言ったように、それはすべてあなたが慣れていることです。Javaのこの柔軟性の欠如は奇妙です。

そうは言っても、そもそもなぜこれがあるのでしょうか?C#は持っているのと同じ理由でpublicinternal(も「何も」)、 、protectedprotected internalprivateが、Javaだけでありpublicprotected、何も、とprivate。追跡する用語やキーワードを増やすことを犠牲にして、コーディング対象の動作をよりきめ細かく制御できます。

newvs. の場合、virtual+override次のようになります。

  • サブクラスにメソッドを強制的に実装させる場合は、サブクラスabstractでおよびoverrideを使用します。
  • 機能を提供したいが、サブクラスがそれを置き換えることを許可する場合は、サブクラスvirtualでおよびoverrideを使用します。
  • サブクラスがオーバーライドする必要のない機能を提供する場合は、何も使用しないでください。
    • あなたは特別なケースサブクラスがある場合はない異なる動作をする必要があるが、使用newサブクラスで。
    • サブクラスが動作をオーバーライドできないようにする場合sealedは、基本クラスで使用します。

実際の例:多くの異なるソースから処理されたeコマースオーダーに取り組んだプロジェクト。OrderProcessorほとんどのロジックを備えたベースがあり、各ソースの子クラスに特定のabstract/ virtualメソッドをオーバーライドします。これは、オーダーを処理する方法がまったく異なる新しいソースを取得するまで、コア機能を置き換える必要があったため、うまくいきました。この時点で2つの選択肢がありました。1)virtualベースメソッドに追加overrideし、子に追加します。または2)new子に追加します。

どちらでも機能します、最初の方法では、将来その特定のメソッドを再度簡単にオーバーライドできます。たとえば、オートコンプリートで表示されます。ただし、これは例外的なケースであったため、new代わりに使用することにしました。これは、「このメソッドをオーバーライドする必要はありません」という標準を維持し、特殊なケースを許可しました。人生を楽にする意味の違いです。

ただし、意味の違いだけでなく、これに関連する動作の違いあることに注意してください。詳細については、この記事を参照してください。ただし、この動作を利用する必要がある状況に遭遇したことはありません。


7
newこの方法を意図的に使用することは、発生を待っているバグだと思います。あるインスタンスでメソッドを呼び出すと、インスタンスをその基本クラスにキャストしたとしても、常に同じことを行うと予想されます。しかし、それはnewedメソッドの動作ではありません。
svick 14年

@svick-可能性としてはい。私たちの特定のシナリオでは、それは決して起こりませんが、だからこそ警告を追加しました。
ボブソン14年

1つの優れた使用法はnew、MVCフレームワークのWebViewPage <TModel>です。しかし、私はnew何時間もかかわるバグによって投げられたので、それは不合理な質問だとは思わない。
pdr 14年

1
@ C.Champagne:どのツールもひどく使用される可能性がありますが、これは特に鋭いツールです-自分で簡単にカットできます。しかし、それはツールボックスからツールを削除し、より才能のあるAPIデザイナーからオプションを削除する理由ではありません。
pdr 14年

1
@svick正しい。これが通常コンパイラの警告である理由です。しかし、「新しい」能力はエッジ条件(指定された条件など)をカバーし、さらに良いことには、避けられないバグを診断するときに奇妙ことをしていることが本当に明白になります。「なぜこのクラスとこのクラスだけがバギーなのか…ああ、「新しい」、スーパークラスが使用されている場所でテストしてみましょう」
deworde

7

Javaの設計では、オブジェクトへの参照が与えられた場合、特定のパラメータータイプを使用した特定のメソッド名の呼び出しが許可されていれば、常に同じメソッドが呼び出されます。暗黙的なパラメーター型の変換が参照の型によって影響を受ける可能性がありますが、そのような変換がすべて解決されると、参照の型は無関係になります。

これにより、ランタイムが簡素化されますが、いくつかの不幸な問題を引き起こす可能性があります。をGrafBase実装しないと仮定しますがvoid DrawParallelogram(int x1,int y1, int x2,int y2, int x3,int y3)GrafDerived計算された4番目の点が最初の点と反対の平行四辺形を描画するパブリックメソッドとして実装します。さらに、の新しいバージョンがGrafBase、同じシグネチャでパブリックメソッドを実装しているが、その計算された4番目のポイントが2番目のポイントと反対であるとします。期待受け取るクライアントGrafBaseが、への参照を受け取るには、GrafDerived期待するDrawParallelogram新しいのファッションに記載のポイントを計算するためGrafBaseの方法が、使用されているクライアントGrafDerived.DrawParallelogramベースの方法が変更された前には行動を期待しますGrafDerived元々実装されています。

Javaでは、作者のための方法はありませんGrafDerived、そのクラスは、新しい使用するクライアントと共存作るためGrafBase.DrawParallelogramの方法を(していることに気付かないかもしれGrafDerived使用されている既存のクライアントコードとの互換性を壊すことなく、でも存在する)GrafDerived.DrawParallelogramGrafBaseにそれを定義しました。DrawParallelogramどの種類のクライアントがそれを呼び出しているかわからないので、種類のクライアントコードによって呼び出された場合、同じように動作する必要があります。2種類のクライアントコードは、動作方法に関して異なる期待を持っているGrafDerivedため、それらの1つの正当な期待に違反する(つまり、正当なクライアントコードを壊す)ことを回避する方法はありません。

C#では、GrafDerived再コンパイルされない場合、ランタイムはDrawParallelogram、型の参照時にメソッドを呼び出すコードが最後にコンパイルされたときGrafDerivedの動作を期待していると想定しますが、型の参照GrafDerived.DrawParallelogram()時にメソッドを呼び出すコードGrafBaseが期待されますGrafBase.DrawParallelogram(追加されました)。場合はGrafDerived、後に強化の存在下で再コンパイルされGrafBase、コンパイラは、プログラマが自分のメソッドが継承されたメンバーのための有効な代替であることを意図していたかどうかを指定するまで、甲高い音になるGrafBase、またはその振る舞いは、タイプの参照に縛られる必要があるかどうかGrafDerived、ではないはずtypeの参照の動作を置き換えGrafBaseます。

同じシグネチャを持つGrafDerivedメンバーとは異なる方法を実行するメソッドを持つことGrafBaseは、設計が悪いことを示すため、サポートされるべきではないと合理的に主張するかもしれません。残念ながら、基本型の作成者は派生型にどのメソッドが追加されるか、またはその逆を知る方法がないため、基本クラスと派生クラスのクライアントが同様の名前のメソッドに対して異なる期待を持っている状況は、他の人が追加する名前を追加することは誰にも許可されていません。問題は、このような名前の重複が発生するかどうかではなく、発生した場合に害を最小限に抑える方法です。


2
ここには良い答えがあると思うが、それはテキストの壁で失われつつある。これをさらにいくつかの段落に分けてください。
ボブソン14年

DerivedGraphics? A class using GrafDerived` とは何ですか?
C.Champagne 14年

@ C.Champagne:という意味GrafDerivedです。を使い始めDerivedGraphicsましたが、少し長いと感じました。でも、GrafDerivedまだ少し長いですが、クリアベース/派生関係を持つべき名前のグラフィックス・レンダラタイプに最善の方法を知りませんでした。
supercat

1
@ボブソン:良いですか?
supercat

6

標準的な状況:

あなたは、複数のプロジェクトで使用される基本クラスの所有者です。あなたは、実世界の価値を提供するプロジェクトにある1と無数の派生クラスの間で壊れる上記の基本クラスに変更を加えたい(フレームワークは、せいぜい価値を提供するだけで、本当の人間はフレームワークを望んでいない、彼らは望むフレームワークで実行されているもの)。派生クラスの忙しい所有者に述べる幸運。「まあ、あなたは変更しなければなりません。あなたは決定を承認しなければならない人々に「フレームワーク:プロジェクトの遅延者とバグの原因者」としての代表を獲得せずに、その方法をオーバーライドするべきではありません。

特に、オーバーライドできないことを宣言しないことで、あなたは暗黙のうちに、変更を防ぐことができることを行うことを宣言しました。

また、基本クラスをオーバーライドすることで現実世界の価値を提供する派生クラスがあまりない場合、なぜそもそも基本クラスなのでしょうか?希望は強力な動機付けですが、参照されていないコードで終わる非常に良い方法でもあります。

最終結果:フレームワークの基本クラスのコードは非常に壊れやすく、静的になります。現在/効率を維持するために必要な変更を実際に加えることはできません。あるいは、フレームワークを使用する主な理由はコーディングをより高速で信頼性の高いものにすることであるため、フレームワークは不安定(派生クラスが壊れ続ける)の担当者を取得し、人々はまったく使用しません。

簡単に言うと、忙しいプロジェクトオーナーに、導入しているバグを修正するためにプロジェクトを遅らせるように依頼することはできません。元の「障害」であっても、彼らのものであり、それはせいぜい議論の余地がある。

そもそも彼らに間違ったことをさせないほうがいい、それは「デフォルトで非仮想」が出てくるところである。そして、誰かがこの特定のメソッドをオーバーライドする必要がある非常に明確な理由であなたに来たとき、そしてなぜ安全でなければなりません。他の人のコードを壊す危険を冒すことなく「ロック解除」できます。


0

デフォルトを非仮想に設定すると、基本クラスの開発者が完璧であると見なされます。私の経験では、開発者は完璧ではありません。基本クラスの開発者がメソッドをオーバーライドしたり、仮想の追加を忘れたりするユースケースを想像できない場合、基本クラスを変更せずに基本クラスを拡張するときに多態性を利用することはできません。実世界では、基本クラスを変更することは多くの場合オプションではありません。

C#では、基本クラス開発者はサブクラス開発者を信頼しません。Javaでは、サブクラス開発者は基本クラス開発者を信頼しません。サブクラスの開発者はサブクラスの責任を負い、適切と思われる場合は基本クラスを拡張する権限を(imho)与えられる必要があります(明示的な拒否がなければ、Javaでもこれが間違っている可能性があります)。

これは、言語定義の基本的なプロパティです。それは正しいか間違っているのではなく、それが何であるかであり、変更することはできません。


2
これは作られたポイントを超える大幅な何かを提供しているようだし、前5つの回答で説明していません
ブヨ
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.