C#がデフォルトでメソッドを非仮想として実装する理由


105

Javaとは異なり、C#はデフォルトでメソッドを非仮想関数として扱うのはなぜですか?他の考えられる結果よりもパフォーマンスの問題である可能性が高いですか?

既存のアーキテクチャがもたらすいくつかの利点について、Anders Hejlsbergからのパラグラフを読んだことを思い出します。しかし、副作用はどうですか?デフォルトで非仮想メソッドを持つことは本当に良いトレードオフですか?


1
パフォーマンス上の理由を述べた回答は、C#コンパイラがほとんどの場合、callではなくcallvirtへのメソッド呼び出しをコンパイルするという事実を見落としています。これが、C#では、this参照がnullの場合に動作が異なるメソッドを持つことができない理由です。詳細はこちらをご覧ください。
アンディ

ほんとだ!IL 呼び出し命令は、主に静的メソッドへの呼び出し用です。
RBT 2017

1
C#の建築家Anders Hejlsbergのここここでの考え。
RBT 2017

回答:


98

クラスは、継承を活用できるように設計する必要があります。virtualデフォルトでメソッドを持つことは、クラス内のすべての関数をプラグアウトして別の関数で置き換えることができることを意味しますが、これは本当に良いことではありません。多くの人は、クラスはsealedデフォルトであるはずだとさえ考えています。

virtualメソッドは、パフォーマンスにわずかな影響を与える可能性もあります。ただし、これが主な理由ではない可能性があります。


7
個人的には、パフォーマンスについてはその部分を疑っています。仮想関数の場合でも、コンパイラーはILコードのcallvirt単純な場所call(またはJITの場合はさらに下流)に置き換える場所を非常に見つけ出すことができます。Java HotSpotも同様です。残りの答えはスポットオンです。
Konrad Rudolph、

5
パフォーマンスは最前線ではありませんが、パフォーマンスにも影響する可能性があります。おそらく非常に小さいでしょう。確かに、それがこの選択の理由ではありません。それについて言及したかっただけです。
Mehrdad Afshari、

71
密封されたクラスは私に赤ちゃんを殴りたくさせます。
mxmissile 2009年

6
@mxmissile:私もですが、良いAPIデザインはとにかく難しいです。デフォルトで封印されていることは、あなたが所有するコードにとって完全に理にかなっていると思います。多くの場合、チームの他のプログラマーは、必要なクラスを実装するときに継承を考慮しませんでした。デフォルトでシールされていると、そのメッセージが非常によく伝わります。
ロマン・スターコフ2010

49
多くの人もサイコパスですが、私たちがそれらに耳を傾ける必要があるという意味ではありません。仮想はC#のデフォルトである必要があります。コードは、実装を変更したり、完全に書き直したりせずに拡張可能である必要があります。APIを過剰に保護している人は、多くの場合、使用されていないAPIを使用することになります。これは、誰かがそれを悪用するシナリオよりもはるかに悪いです。
Chris Nicola

89

ここでは、デフォルトではない仮想が物事を行うための正しい方法であるというコンセンサスがあるように思われます。私は反対側に降りていきます-私は実用的だと思います-フェンスの側。

正当化のほとんどは、古い「私たちがあなたに力を与えるならば、あなたはあなた自身を傷つけるかもしれない」という議論のように私に読みました。プログラマーから?!

継承や拡張性のためにライブラリを設計するのに十分な(または十分な時間がない)コーダーは、私が修正または微調整しなければならない可能性が高いライブラリを正確に作成したコーダーであるように思えます-正確にオーバーライドする機能が最も役立つライブラリ。

私がこれまでにかまれた回数をはるかに上回らないため、醜い、絶望的な回避策のコードを書く必要があった回数(または使用を中止して独自の代替ソリューションをロールバックした回数)例:Javaの場合)設計者が考慮していない可能性のある場所をオーバーライドする。

デフォルトでは非仮想なので、私の人生は困難になります。

更新:私が実際に質問に答えなかったことが[かなり正確に]指摘されています。だから-そして、かなり遅れたことをお詫びして...

「プログラマよりもプログラムを高く評価する誤った決定がなされたため、C#はデフォルトで非仮想メソッドを実装する」のような簡潔なものを書けるようにしたいと思っていました。(私はそれがこの質問の他のいくつかの回答に基づいていくらか正当化されると思います-パフォーマンス(時期尚早の最適化、誰か?)、またはクラスの動作の保証など)

ただし、私は自分の意見を述べているだけで、Stack Overflowが望んでいる決定的な答えではないことを理解しています。確かに、私は、最高レベルでの決定的な(しかし役に立たない)答えは次のとおりだと思いました。

言語デザイナーは決断を下したため、デフォルトでは非仮想です。

彼らがその決定を下した正確な理由は私には決してないだろうと思います。 会話の筆記録!

したがって、APIをオーバーライドすることの危険性と継承を明示的に設計する必要性についてのここでの回答とコメントは正しい道にありますが、重要な一時的な側面がすべて欠けているように見えます。Andersの主な懸念は、クラスまたはAPIの暗黙の維持に関するものでしたバージョン間の契約。そして、実際には、プラットフォーム上でのユーザーコードの変更についてではなく、.Net / C#プラットフォームがコードの下で変更できるようにすることについて、彼はもっと心配していると思います。(そして、彼の「プラグマティック」な視点は、反対側から見ているので、私の正反対です。)

(しかし、デフォルトでvirtual-by-defaultを選択し、コードベースを介して "最終"を実行したのではないでしょうか?おそらくそれはまったく同じではありません。そして、Andersは私より明らかに賢いので、嘘をつきます。)


2
これ以上同意できませんでした。サードパーティのAPIを使用していて、一部の動作をオーバーライドしたいができなかった場合、非常にイライラします。
アンディ

3
私は熱心に同意します。APIを公開する場合(会社の内部でも外部の世界でも)、実際にコードを継承できるように設計する必要があります。多くの人が使用できるようにAPIを公開する場合は、APIを優れた洗練されたものにする必要があります。優れた全体的な設計に必要な労力(優れたコンテンツ、明確なユースケース、テスト、ドキュメンテーション)と比較すると、継承のための設計はそれほど悪くありません。また、公開していない場合は、virtual-by-defaultの方が時間がかかりません。問題がある場合は、いつでも小さな部分を修正できます。
重力

2
ここで、すべてのメソッド/プロパティに自動的にタグを付けるVisual Studioのエディター機能しかなかった場合virtual... Visual Studioアドイン誰か?
kevinarpe

1
私は心からあなたに同意しますが、これは正確な答えではありません。
Chris Morgan

2
依存関係の注入、モックフレームワーク、ORMなどの最新の開発プラクティスを考えると、C#の設計者が少し見逃していることは明らかです。デフォルトでプロパティをオーバーライドできない場合、依存関係をテストする必要があるのは非常にイライラします。
Jeremy Holovacs 2013

17

メソッドがオーバーライドされ、そのために設計されていない可能性があることを忘れるのは簡単すぎるためです。C#を使用すると、仮想化する前に考えることができます。これは素晴らしい設計上の決定だと思います。一部の人々(Jon Skeetなど)は、クラスはデフォルトでシールされる必要があるとさえ言っています。


12

他の人が言ったことを要約すると、いくつかの理由があります:

1- C#では、構文とセマンティクスには、C ++から直接もたらされる多くのものがあります。C ++のデフォルトで仮想ではないメソッドがC#に影響を与えたという事実。

2-すべてのメソッド呼び出しはオブジェクトの仮想テーブルを使用する必要があるため、デフォルトですべてのメソッドを仮想にすることはパフォーマンスの問題です。さらに、これにより、Just-In-Timeコンパイラがメソッドをインライン化し、他の種類の最適化を実行する機能が大幅に制限されます。

3-最も重要なのは、メソッドがデフォルトで仮想ではない場合、クラスの動作を保証できることです。それらがJavaなどのデフォルトで仮想である場合、派生クラスで何かを実行するためにオーバーライドされる可能性があるため、単純なgetterメソッドが意図したとおりに実行されることを保証することもできません(もちろん、メソッドおよび/またはクラスfinal)。

Zifreが述べたように、なぜC#言語が一歩先を行き、デフォルトでクラスをシールしないのか不思議に思うかもしれません。これは、非常に興味深いトピックである、実装継承の問題に関する議論全体の一部です。


1
私もデフォルトで密封された優先クラスを持っているでしょう。その後、それは一貫しています。
アンディ14年

9

C#はC ++(およびその他)の影響を受けます。C ++は、デフォルトでは動的ディスパッチ(仮想関数)を有効にしません。これに対する(良い?)議論の1つは、「クラス階層のメンバーであるクラスをどのくらいの頻度で実装するか」という質問です。デフォルトで動的ディスパッチを有効にしない別の理由は、メモリーのフットプリントです。もちろん、仮想テーブルを指す仮想ポインター(vpointer)のないクラスは、遅延バインディングが有効になっている対応するクラスよりも小さくなります。

パフォーマンスの問題は、「はい」または「いいえ」と言うのは簡単ではありません。この理由は、C#でのランタイム最適化であるJust In Time(JIT)コンパイルです。

仮想通話の速度..」についての別の同様の質問


JITコンパイラーが原因で、仮想メソッドがC#のパフォーマンスに影響を与えることには少し疑問があります。彼らは、実行する前に、「不明」です関数呼び出しインライン化することができますので、それは、JITコンパイルがオフラインよりも改善することができます地域の一つだ
デビッドCournapeauを

1
実際、デフォルトでそうするC ++よりJavaの影響が大きいと思います。
Mehrdad Afshari、

5

単純な理由は、パフォーマンスコストに加えて、設計とメンテナンスのコストです。クラスの設計者は、メソッドが別のクラスによってオーバーライドされたときに何が起こるかを計画する必要があるため、仮想メソッドは非仮想メソッドと比較して追加のコストがかかります。特定のメソッドで内部状態を更新したり、特定の動作を期待したりする場合、これは大きな影響を与えます。次に、派生クラスがその動作を変更したときに何が起こるかを計画する必要があります。そのような状況では、信頼性の高いコードを書くことははるかに困難です。

非仮想メソッドを使用すると、完全に制御できます。うまくいかなかったことは、元の作者の責任です。コードの方がはるかに簡単です。


これは本当に古い投稿ですが、彼の最初のプロジェクトを書いている初心者のために、私は自分が書いたコードの意図しない/不明な結果を常に心配しています。非仮想メソッドが完全に私のせいであることを知っていることは非常に慰めです。
trevorc

2

すべてのC#メソッドが仮想である場合、vtblははるかに大きくなります。

クラスに仮想メソッドが定義されている場合、C#オブジェクトには仮想メソッドしかありません。すべてのオブジェクトがvtblに相当するタイプ情報を持っていることは事実ですが、仮想メソッドが定義されていない場合は、基本オブジェクトメソッドのみが存在します。

@Tom Hawtin:C ++、C#、Javaはすべて言語のCファミリーに属していると言った方が正確でしょう:)


1
vtableが非常に大きくなることが問題になるのはなぜですか?クラスごとに(インスタンスごとではなく)1つのvtableしかないため、そのサイズはそれほど大きな違いはありません。
重力、

1

perlの背景から来たので、C#は、新しいクラスのすべてのユーザーに背後で潜在的に気づかせることなく、基本クラスの動作を非仮想メソッドを介して拡張および変更したいすべての開発者の運命を封じたと思います詳細。

ListクラスのAddメソッドを考えます。特定のリストが「追加」されるたびに、開発者がいくつかの潜在的なデータベースの1つを更新したい場合はどうなりますか?'Add'がデフォルトで仮想であった場合、開発者はすべてのクライアントコードに通常の 'List'ではなく 'BackedList'であることを知らせずに 'Add'メソッドをオーバーライドする 'BackedList'クラスを開発できます。すべての実用的な目的で、「BackedList」はクライアントコードからの単なる別の「リスト」と見なすことができます。

これは、データベース内の1つ以上のスキーマによってサポートされている1つ以上のリストコンポーネントへのアクセスを提供する可能性がある大きなメインクラスの観点からは理にかなっています。C#メソッドはデフォルトでは仮想ではないため、メインクラスによって提供されるリストは、単純なIEnumerableまたはICollection、さらにはListインスタンスにすることはできませんが、新しいバージョンを確実にするために、「BackedList」としてクライアントにアドバタイズする必要があります正しいスキーマを更新するために、「追加」操作の呼び出しが行われます。


真の、仮想メソッドはデフォルトで仕事を簡単にしますが、それは意味がありますか?生活を楽にするために採用できることはたくさんあります。なぜクラスのパブリックフィールドだけではないのですか?その動作を変更するのは簡単すぎます。私の意見では、言語のすべては、デフォルトで変更するために厳密にカプセル化され、固定され、回復力があるべきです。必要な場合のみ変更してください。ちょうどリルの哲学的なabデザインの決定です。続き..
nawfal 2013年

...現在のトピックについて話すには、継承モデルはがの場合にのみ使用する必要BがありAます。それBとは異なる何かが必要な場合はA、その必要はありませんA。言語自体の機能としてのオーバーライドは、設計上の欠陥であると私は考えています。別のAddメソッドが必要な場合、コレクションクラスはではありませんList。それを伝えることは偽物です。ここでの正しいアプローチは、構成です(偽造ではありません)。確かにフレームワーク全体はオーバーライド機能に基づいて構築されていますが、私はそれが好きではありません。
nawfal

私はあなたの要点を理解していると思いますが、提供された具体的な例では、「BackedList」はインターフェース「IList」を実装するだけでよく、クライアントはインターフェースについてしか知りません。正しい?私は何かを逃していますか?しかし、私はあなたが主張しようとしているより広いポイントを理解しています。
Vetras

0

それは確かにパフォーマンスの問題ではありません。SunのJavaインタープリターは同じコードを使用してディスパッチし(invokevirtualバイトコード)、HotSpotはまったく同じコードを生成しfinalます。すべてのC#オブジェクト(構造体ではない)には仮想メソッドがあると思います。そのため、常にvtbl/ runtimeクラスID が必要になります。C#は「Javaのような言語」の方言です。それがC ++からのものであることを示唆することは、完全に正直ではありません。

「継承のために設計するか、それを禁止する」べきだという考えがあります。迅速な解決策を講じる深刻なビジネスケースがある瞬間まで、これは素晴らしいアイデアのように思えます。おそらく、あなたが制御していないコードから継承するでしょう。


すべてのメソッドがデフォルトで仮想であり、パフォーマンスに大きな影響を与えるため、ホットスポットはその最適化を実行するために膨大な時間を費やす必要があります。CoreCLRは、はるかにシンプルでありながら、同様のパフォーマンスを実現できます
Yair Halberstadt

@YairHalberstadt Hotspotは、他のさまざまな理由でコンパイル済みコードをバックアウトできる必要があります。ソースを見てから数年が経ちましたが、finalと効果的なfinal方法の違いはごくわずかです。また、2種類の実装を持つインラインメソッドであるバイモルフィックインライン化を実行できることにも注意してください。
トムホーティン-タックライン
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.