19

重複の可能性:
C#がデフォルトでメソッドを非仮想として実装するのはなぜですか?

私は主にC#、. NET 3.5について話していますが、一般に、すべてを「仮想」と見なさないことの利点は何でしょうか。つまり、子クラスのインスタンスで呼び出されるメソッドは、常に子のほとんどのバージョンを実行します。その方法の。C#では、親メソッドが「仮想」修飾子でラベル付けされていない場合、これは当てはまりません。例:

public class Parent
{
    public void NonVirtual() { Console.WriteLine("Non-Virtual Parent"); }
    public virtual void Virtual(){ Console.WriteLine("Virtual Parent"); }
}

public class Child : Parent
{
    public new void NonVirtual() { Console.WriteLine("Non-Virtual Child"); }
    public override void Virtual() { Console.WriteLine("Virtual Child"); }
}

public class Program
{
    public static void Main(string[] args)
    {
        Child child = new Child();
        Parent parent = new Child();
        var anon = new Child();

        child.NonVirtual();           // => Child
        parent.NonVirtual();          // => Parent
        anon.NonVirtual();            // => Child
        ((Parent)child).NonVirtual(); // => Parent

        child.Virtual();              // => Child
        parent.Virtual();             // => Child
        anon.Virtual();               // => Child
        ((Parent)child).Virtual();    // => Child
    }
}

上記で観察された非仮想動作の利点は正確には何ですか?私が考えることができた唯一のことは、「親の作者が彼のメソッドを仮想化したくない場合はどうなるか」ということでした。しかし、そのとき、私はそのための良いユースケースを考えることができないことに気づきました。クラスの動作は非仮想メソッドの動作に依存していると主張する人もいるかもしれませんが、カプセル化が不十分であるか、メソッドを封印する必要があるように思われます。

これらの同じ線に沿って、「隠す」は通常悪い考えのようです。結局のところ、Childオブジェクトとメソッドが作成された場合、Parentをオーバーライドする特定の理由で作成されたようです。また、ChildがNonVirtual()を実装(および親を非表示)する場合、Child :: NonVirtual()を呼び出すことの「予想される」動作を多くの人が考える可能性のあるものを取得しないのは非常に簡単です。(「非表示」が発生していることに気付かないことがあるため、「期待」と言います)。

では、すべてに「仮想」動作を許可しないことの利点は何でしょうか。予期しない動作が発生しやすい場合に、非仮想の親を非表示にするための適切なユースケースは何ですか?

なぜ私がこの質問をするのか知りたい人がいたら、私は最近CastleProjectsDynamicProxyライブラリを調べていました。これを使用する際の主なハードルの1つは、プロキシするメソッド(またはプロパティ)が仮想である必要があることです。そして、これは開発者にとって常にオプションであるとは限りません(ソースを制御できない場合)。言うまでもなく、DynamicProxyの目的は、プロキシされたクラスと、プロキシで達成しようとしている動作(Loggingやメモ化の実装など)との結合を回避することです。そして、仮想メソッドにこれを強制的に実行させることで、代わりに達成されることは非常に薄いですが、DynamicProxyをプロキシしているすべてのクラスに鈍く結合します-想像してみてください。継承およびオーバーライドされることはありませんが、virtualというラベルの付いたメソッドがたくさんあります。

とにかく、そこでの欲求不満は、すべてが仮想であることがより明確であり(IMO、私は推測する)、おそらく(?)より多くの利点があるように見えるときに、非仮想の利点は何であるか疑問に思いました。

編集:主観的な答えがあるかもしれない質問のように見えるので、コミュニティウィキとしてラベル付けする

4

6 に答える 6

9

クラスを設計していないメソッドをオーバーライドしてほしくないからです。メソッドをオーバーライドしたり、クラスから派生させたりしても安全であることを確認するには、かなりの労力が必要です。virtual何が起こるかを考えていなければ、非にする方がはるかに安全です。

于 2009-06-30T21:44:33.240 に答える
7

Eric Lippert は、メソッドの隠蔽について、ここでこれをカバーしています

于 2009-06-30T21:36:03.820 に答える
3

多くの場合、クラスが適切に機能するためには、特定のメソッドが特定の動作をすることが重要です。メソッドが継承されたクラスでオーバーライドされた場合、メソッドが期待される動作を正しく実装するという保証はありません。クラスが継承用に特別に設計されており、異なる実装のメソッドをサポートする場合にのみ、メソッドを virtual とマークする必要があります。継承のための設計は容易ではありません。メソッドを誤ってオーバーライドすると、クラスの内部動作が壊れてしまうことがよくあります。

于 2009-06-30T21:38:18.027 に答える
2

シンプル: クラスの要点は、ある種の抽象化をカプセル化することです。たとえば、テキスト文字列として動作するオブジェクトが必要です。

さて、すべてが仮想化されていれば、次のことができます。

class MessedUpString : String{
   override void Trim() { throw new Exception(); }
}

そして、これを文字列を期待する関数に渡します。そして、その弦を切ろうとした瞬間、爆発します。

文字列は文字列として動作しなくなります。それはどうして良いことですか

すべてが仮想化されると、クラスの不変条件を強制するのに苦労することになります。クラスの抽象化を破ることができます。

デフォルトでは、クラスは、従うことが期待されるルールと動作をカプセル化する必要があります。仮想化するものはすべて、原則として拡張フックであり、機能を変更して何でも行うことができます。これは、実際にユーザーが定義した動作がある場合にのみ意味があります。

クラスが便利な理由は、実装の詳細を無視できるからです。「これは文字列オブジェクトです。文字列として動作することはわかっています。これらの保証のいずれにも違反しないことはわかっています」と簡単に言えます。その保証が維持できない場合、そのクラスは役に立たない。すべてのデータ メンバーを public にして、メンバー メソッドをクラスの外に移動することもできます。

リスコフの置換原理を知っていますか?基本クラス B のオブジェクトが予期される場所であればどこでも、派生クラス D のオブジェクトを渡すことができるはずです。これは、オブジェクト指向プログラミングの最も基本的なルールの 1 つです。派生クラスを基本クラスにアップキャストし、基本クラスを期待する関数に渡す場合でも、派生クラスが機能することを知っておく必要があります。つまり、一部の動作を固定して変更できないようにする必要があります。

于 2009-06-30T21:42:59.717 に答える
0

非仮想メソッドの主な利点の 1 つは、コンパイル時にバインドできることです。つまり、コンパイラは、メソッドがコードで使用されているときに、どの実際のメソッドが呼び出されるかを確認できます。

呼び出される実際のメソッドは、そのメソッドが virtual と宣言されている場合、コンパイル時に認識できません。これは、参照が、それをオーバーライドしたサブタイプを実際に指している可能性があるためです。したがって、呼び出す実際のメソッドを解決する必要がある場合、実行時にわずかなオーバーヘッドが発生します。

于 2009-06-30T21:34:32.107 に答える
0

フレームワークでは、メソッドが仮想である場合、非仮想メンバーを呼び出して、期待される出力の範囲を持つことができます。メソッドの結果は、テストされていない期待される結果になる可能性があります。メソッドが非仮想であることを許可すると、フレームワーク アクションに期待される結果が得られます。

于 2009-06-30T21:40:19.553 に答える