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#new
とoverride
C#の違いを知らない人は、この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
、基本メソッドの機能が派生メソッドと異なっていても、クライアントを壊しません。参照したコードVehicle
はCar.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()
ます。