105

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

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

4

10 に答える 10

89

ここで、デフォルトで非仮想化が正しい方法であるというコンセンサスがあるように見えることに驚いています。私は反対側に降りるつもりです-私は実用的だと思います-フェンスの側。

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

継承および/または拡張性のためにライブラリを設計するのに十分な知識がなかった (または十分な時間がない) コーダーは、私が修正または微調整する必要がある可能性が高いライブラリを正確に作成したコーダーのように思えます。オーバーライドする機能が最も役立つライブラリ。

はるかにオーバーライドできないために、醜くて絶望的な回避策のコードを書かなければならなかった回数 (または、使用を放棄して独自の代替ソリューションを展開する必要があった回数) は、これまでに噛まれた回数をはるかに上回ります (たとえば Java で) デザイナーが考えていなかったかもしれない場所をオーバーライドすることによって。

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

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

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

ただし、私は自分の意見を述べているだけであり、スタック オーバーフローが望んでいる決定的な答えではないことに気付きました。確かに、最高レベルで決定的な(しかし役に立たない)答えは次のとおりだと思いました。

言語設計者が決定を下し、それを選択したため、デフォルトでは非仮想です。

彼らがその決定を下した正確な理由を推測します....ああ、待ってください! 会話の書き起こし!

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

(しかし、彼らはデフォルトで virtual-by-default を選択してから、コードベースに「final」を追加したのではないでしょうか? おそらくそれはまったく同じではありません.. Anders は明らかに私より賢いので、嘘をつくことにします.)

于 2010-06-18T11:23:02.417 に答える
12

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

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

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

3-最も重要なのは、メソッドがデフォルトで仮想でない場合、クラスの動作を保証できることです。Java のように、それらがデフォルトで仮想である場合、単純な getter メソッドが意図したとおりに機能することを保証することさえできません。メソッドおよび/またはクラス final)。

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

于 2009-05-02T14:41:39.063 に答える
9

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

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

「仮想通話の速度..」に関する別の同様の質問

于 2009-05-02T14:29:40.543 に答える
5

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

非仮想メソッドを使用すると、完全に制御できます。うまくいかないのは、原作者のせいです。コードははるかに簡単に推論できます。

于 2009-05-02T14:46:27.260 に答える
1

Perl のバックグラウンドから来て、C# は、新しいクラスのすべてのユーザーに潜在的な背後にあることを認識させることなく、非仮想メソッドを介して基本クラスの動作を拡張および変更したいと考えていたすべての開発者の運命を封印したと思います詳細。

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

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

于 2010-10-28T21:21:59.957 に答える
0

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

「継承するように設計するか、禁止するか」という考え方があります。これは、迅速な修正が必要な深刻なビジネス ケースがある瞬間までは、素晴らしいアイデアのように思えます。おそらく、あなたが制御していないコードから継承しています。

于 2009-05-02T14:47:46.773 に答える