Javaとは異なり、C#はデフォルトでメソッドを非仮想関数として扱うのはなぜですか?他の考えられる結果よりもパフォーマンスの問題である可能性が高いですか?
既存のアーキテクチャがもたらすいくつかの利点について、Anders Hejlsbergからのパラグラフを読んだことを思い出します。しかし、副作用はどうですか?デフォルトで非仮想メソッドを持つことは本当に良いトレードオフですか?
Javaとは異なり、C#はデフォルトでメソッドを非仮想関数として扱うのはなぜですか?他の考えられる結果よりもパフォーマンスの問題である可能性が高いですか?
既存のアーキテクチャがもたらすいくつかの利点について、Anders Hejlsbergからのパラグラフを読んだことを思い出します。しかし、副作用はどうですか?デフォルトで非仮想メソッドを持つことは本当に良いトレードオフですか?
回答:
クラスは、継承を活用できるように設計する必要があります。virtual
デフォルトでメソッドを持つことは、クラス内のすべての関数をプラグアウトして別の関数で置き換えることができることを意味しますが、これは本当に良いことではありません。多くの人は、クラスはsealed
デフォルトであるはずだとさえ考えています。
virtual
メソッドは、パフォーマンスにわずかな影響を与える可能性もあります。ただし、これが主な理由ではない可能性があります。
callvirt
単純な場所call
(またはJITの場合はさらに下流)に置き換える場所を非常に見つけ出すことができます。Java HotSpotも同様です。残りの答えはスポットオンです。
ここでは、デフォルトではない仮想が物事を行うための正しい方法であるというコンセンサスがあるように思われます。私は反対側に降りていきます-私は実用的だと思います-フェンスの側。
正当化のほとんどは、古い「私たちがあなたに力を与えるならば、あなたはあなた自身を傷つけるかもしれない」という議論のように私に読みました。プログラマーから?!
継承や拡張性のためにライブラリを設計するのに十分な(または十分な時間がない)コーダーは、私が修正または微調整しなければならない可能性が高いライブラリを正確に作成したコーダーであるように思えます-正確にオーバーライドする機能が最も役立つライブラリ。
私がこれまでにかまれた回数をはるかに上回らないため、醜い、絶望的な回避策のコードを書く必要があった回数(または使用を中止して独自の代替ソリューションをロールバックした回数)例:Javaの場合)設計者が考慮していない可能性のある場所をオーバーライドする。
デフォルトでは非仮想なので、私の人生は困難になります。
更新:私が実際に質問に答えなかったことが[かなり正確に]指摘されています。だから-そして、かなり遅れたことをお詫びして...
「プログラマよりもプログラムを高く評価する誤った決定がなされたため、C#はデフォルトで非仮想メソッドを実装する」のような簡潔なものを書けるようにしたいと思っていました。(私はそれがこの質問の他のいくつかの回答に基づいていくらか正当化されると思います-パフォーマンス(時期尚早の最適化、誰か?)、またはクラスの動作の保証など)
ただし、私は自分の意見を述べているだけで、Stack Overflowが望んでいる決定的な答えではないことを理解しています。確かに、私は、最高レベルでの決定的な(しかし役に立たない)答えは次のとおりだと思いました。
言語デザイナーは決断を下したため、デフォルトでは非仮想です。
彼らがその決定を下した正確な理由は私には決してないだろうと思います。 会話の筆記録!
したがって、APIをオーバーライドすることの危険性と継承を明示的に設計する必要性についてのここでの回答とコメントは正しい道にありますが、重要な一時的な側面がすべて欠けているように見えます。Andersの主な懸念は、クラスまたはAPIの暗黙の維持に関するものでしたバージョン間の契約。そして、実際には、プラットフォーム上でのユーザーコードの変更についてではなく、.Net / C#プラットフォームがコードの下で変更できるようにすることについて、彼はもっと心配していると思います。(そして、彼の「プラグマティック」な視点は、反対側から見ているので、私の正反対です。)
(しかし、デフォルトでvirtual-by-defaultを選択し、コードベースを介して "最終"を実行したのではないでしょうか?おそらくそれはまったく同じではありません。そして、Andersは私より明らかに賢いので、嘘をつきます。)
virtual
... Visual Studioアドイン誰か?
他の人が言ったことを要約すると、いくつかの理由があります:
1- C#では、構文とセマンティクスには、C ++から直接もたらされる多くのものがあります。C ++のデフォルトで仮想ではないメソッドがC#に影響を与えたという事実。
2-すべてのメソッド呼び出しはオブジェクトの仮想テーブルを使用する必要があるため、デフォルトですべてのメソッドを仮想にすることはパフォーマンスの問題です。さらに、これにより、Just-In-Timeコンパイラがメソッドをインライン化し、他の種類の最適化を実行する機能が大幅に制限されます。
3-最も重要なのは、メソッドがデフォルトで仮想ではない場合、クラスの動作を保証できることです。それらがJavaなどのデフォルトで仮想である場合、派生クラスで何かを実行するためにオーバーライドされる可能性があるため、単純なgetterメソッドが意図したとおりに実行されることを保証することもできません(もちろん、メソッドおよび/またはクラスfinal)。
Zifreが述べたように、なぜC#言語が一歩先を行き、デフォルトでクラスをシールしないのか不思議に思うかもしれません。これは、非常に興味深いトピックである、実装継承の問題に関する議論全体の一部です。
C#はC ++(およびその他)の影響を受けます。C ++は、デフォルトでは動的ディスパッチ(仮想関数)を有効にしません。これに対する(良い?)議論の1つは、「クラス階層のメンバーであるクラスをどのくらいの頻度で実装するか」という質問です。デフォルトで動的ディスパッチを有効にしない別の理由は、メモリーのフットプリントです。もちろん、仮想テーブルを指す仮想ポインター(vpointer)のないクラスは、遅延バインディングが有効になっている対応するクラスよりも小さくなります。
パフォーマンスの問題は、「はい」または「いいえ」と言うのは簡単ではありません。この理由は、C#でのランタイム最適化であるJust In Time(JIT)コンパイルです。
「仮想通話の速度..」についての別の同様の質問
単純な理由は、パフォーマンスコストに加えて、設計とメンテナンスのコストです。クラスの設計者は、メソッドが別のクラスによってオーバーライドされたときに何が起こるかを計画する必要があるため、仮想メソッドは非仮想メソッドと比較して追加のコストがかかります。特定のメソッドで内部状態を更新したり、特定の動作を期待したりする場合、これは大きな影響を与えます。次に、派生クラスがその動作を変更したときに何が起こるかを計画する必要があります。そのような状況では、信頼性の高いコードを書くことははるかに困難です。
非仮想メソッドを使用すると、完全に制御できます。うまくいかなかったことは、元の作者の責任です。コードの方がはるかに簡単です。
すべてのC#メソッドが仮想である場合、vtblははるかに大きくなります。
クラスに仮想メソッドが定義されている場合、C#オブジェクトには仮想メソッドしかありません。すべてのオブジェクトがvtblに相当するタイプ情報を持っていることは事実ですが、仮想メソッドが定義されていない場合は、基本オブジェクトメソッドのみが存在します。
@Tom Hawtin:C ++、C#、Javaはすべて言語のCファミリーに属していると言った方が正確でしょう:)
perlの背景から来たので、C#は、新しいクラスのすべてのユーザーに背後で潜在的に気づかせることなく、基本クラスの動作を非仮想メソッドを介して拡張および変更したいすべての開発者の運命を封じたと思います詳細。
ListクラスのAddメソッドを考えます。特定のリストが「追加」されるたびに、開発者がいくつかの潜在的なデータベースの1つを更新したい場合はどうなりますか?'Add'がデフォルトで仮想であった場合、開発者はすべてのクライアントコードに通常の 'List'ではなく 'BackedList'であることを知らせずに 'Add'メソッドをオーバーライドする 'BackedList'クラスを開発できます。すべての実用的な目的で、「BackedList」はクライアントコードからの単なる別の「リスト」と見なすことができます。
これは、データベース内の1つ以上のスキーマによってサポートされている1つ以上のリストコンポーネントへのアクセスを提供する可能性がある大きなメインクラスの観点からは理にかなっています。C#メソッドはデフォルトでは仮想ではないため、メインクラスによって提供されるリストは、単純なIEnumerableまたはICollection、さらにはListインスタンスにすることはできませんが、新しいバージョンを確実にするために、「BackedList」としてクライアントにアドバタイズする必要があります正しいスキーマを更新するために、「追加」操作の呼び出しが行われます。
B
がありA
ます。それB
とは異なる何かが必要な場合はA
、その必要はありませんA
。言語自体の機能としてのオーバーライドは、設計上の欠陥であると私は考えています。別のAdd
メソッドが必要な場合、コレクションクラスはではありませんList
。それを伝えることは偽物です。ここでの正しいアプローチは、構成です(偽造ではありません)。確かにフレームワーク全体はオーバーライド機能に基づいて構築されていますが、私はそれが好きではありません。
それは確かにパフォーマンスの問題ではありません。SunのJavaインタープリターは同じコードを使用してディスパッチし(invokevirtual
バイトコード)、HotSpotはまったく同じコードを生成しfinal
ます。すべてのC#オブジェクト(構造体ではない)には仮想メソッドがあると思います。そのため、常にvtbl
/ runtimeクラスID が必要になります。C#は「Javaのような言語」の方言です。それがC ++からのものであることを示唆することは、完全に正直ではありません。
「継承のために設計するか、それを禁止する」べきだという考えがあります。迅速な解決策を講じる深刻なビジネスケースがある瞬間まで、これは素晴らしいアイデアのように思えます。おそらく、あなたが制御していないコードから継承するでしょう。
final
と効果的なfinal
方法の違いはごくわずかです。また、2種類の実装を持つインラインメソッドであるバイモルフィックインライン化を実行できることにも注意してください。
this
参照がnullの場合に動作が異なるメソッドを持つことができない理由です。詳細はこちらをご覧ください。