3

私はWindows.Formsカスタム動作を提供するためにいくつかのコントロールを使用しており、継承する必要があります。この継承は明らかにメソッドのオーバーライドにつながります。

それで、ここに質問があります-どの場合、呼び出しの順序はbase.OnSomething(...)あなたのプログラムの目に見える振る舞いに本当に影響を与える可能性がありますか?

protected override OnRetrieveVirtualItem(RetrieveVirtualItemEventArgs e)
{
    // base.OnRetrieveVirtualItem(e); - Could this potentially break something?

    // Perform your custom method.
    // ...

    base.OnRetrieveVirtualItem(e); // Is this always "correct"?
}

私の知る限り、ペイント関連のメソッド()をオーバーライドする場合、この順序は重要になる可能性がありますが、管理されていないコード呼び出しが多く、副作用が発生する可能性があるため、脚を撃つ方法は他OnDrawItem, ...にもあると思います。Windows.Forms

それで、それはいつ問題になる可能性がありますか?baseそして、これらの場合にメソッドを呼び出す正しい場所を選択するための経験則は何ですか?

4

4 に答える 4

3

base.SomeVirtualMethodそのAPIのドキュメントで、そうする必要があると指定されている場合にのみ、を呼び出す必要があります。それ以外の場合は、オプションとして暗示する必要があります。基本メソッドを呼び出す必要があるが、明示的には述べていないAPIは、不適切に設計されています。

基本呼び出しが必要な理由は、設計が不十分であるためです。これは、メソッドをオーバーライドする誰かが何をするかを期待できず、必要なコードや重要なコードを実行するために基本メソッドを呼び出すかどうかを確信できないためです。

つまり、ドキュメントを参照してください。そうでない場合は、通常は必要ありません。.NET Frameworkはこのようなガイドラインに基づいて設計されており、ほとんどの仮想メソッドでは、これらの理由からベースを呼び出す必要はありません。そうするものは文書化されています。

基本仮想メソッドを呼び出す非常に重要な理由を指摘してくれたrokenに感謝します。それは、イベントを使用するときです。ただし、これが常に当てはまるとは限らないという私の反論は、特に.NETのイディオムとパターンに従わないサードパーティのライブラリまたはクラスを使用している場合は、確実性がありません。この例を見てください。

namespace ConsoleApplication12
{
    using System;
    using System.Diagnostics;

    class Foo
    {
        public Foo() {
        }

        public event EventHandler Load;

        protected virtual void OnLoad() {
            EventHandler handler = Load;

            if (handler != null) {
                handler(this, new EventArgs());
            }

            Debug.WriteLine("Invoked Foo.OnLoad");
        }

        public void Run() {
            OnLoad();
        }
    }

    class DerivedFoo : Foo
    {
        protected override void OnLoad() {
            base.OnLoad();
            Debug.WriteLine("Invoked DerivedFoo.OnLoad");
        }
    }

    class Program
    {
        static void Main(string[] args) {
            DerivedFoo dFoo = new DerivedFoo();

            dFoo.Load += (sender, e) => {
                Debug.WriteLine("Invoked dFoo.Load subscription");    
            };

            dFoo.Run();
        }
    }
}

この例を実行すると、、、およびイベントサブスクリプションへの3つの呼び出しが得Foo.OnLoadられDerivedFoo.OnLoadますdFoo.Load。の呼び出しをコメントアウトすると、へのbase.OnLoad呼び出しDerivedFooは1回だけにDerivedFoo.OnLoadなり、ベースとサブスクライバーは呼び出されませんでした。

その要点は、ドキュメンテーション次第であるということは依然として強いです。基本仮想メソッドの実装がサブスクライバーを呼び出すかどうかはまだわかりません。ですから、それは明らかなはずです。幸いなことに、.NET Frameworkは、フレームワーク設計者のおかげで.NETイベントモデルと非常に一貫性がありますが、APIのドキュメントを常に読むのに十分なストレスを感じることはできません。

これは、イベントをまったく処理していないときに多くの効果を発揮しますが、抽象基本クラスのようなものです。抽象クラスの基本イベントを呼び出すかどうかをどうやって知るのですか?抽象クラスはデフォルトの実装を提供しますか、それともあなたがそれを提供することを期待していますか?

ドキュメントは、仮想メンバーの契約を定義するための最も強力で明確な方法です。これが、.NET Frameworkデザイナーチームが、出荷される抽象クラスに少なくとも1つの具体的な実装を提供する理由の1つです。

Krzysztof Cwalinaは、フレームワークの設計ガイドラインでそれを最もよく言っていると思います。

私がよく受ける質問は、仮想メンバーのドキュメントに、オーバーライドで基本実装を呼び出す必要があると記載する必要があるかどうかです。答えは、オーバーライドは基本クラスのコントラクトを保持する必要があるということです。基本実装を呼び出すか、他の方法でそれを行うことができます。メンバーが(オーバーライドで)そのコントラクトを保持する唯一の方法がそれを呼び出すことであると主張できることはまれです。多くの場合、ベースを呼び出すことが契約を維持するための最も簡単な方法かもしれません(そしてドキュメントはそれを指摘する必要があります)が、それが絶対に必要になることはめったにありません。

そして、私は完全に同意します。基本実装をオーバーライドして呼び出さないことにした場合は、同じ機能を提供する必要があります。

これにより、コメントでの混乱が解消されることを願っています。

于 2012-04-30T10:07:10.750 に答える
3

WinFormsの「経験則」として、On [EventName](つまり、OnFormClosing)メソッドを使用して、対応するイベントがフレームワーククラスによって発生するように、基本メソッドを呼び出す必要があります(そうでない場合、イベントはによって発生しません)。制御)。悪いデザインであろうとなかろうと、それは非常に一般的なパターンです。

于 2012-04-30T11:13:24.383 に答える
2

一般に、最初に基本メソッドを呼び出すことをお勧めします。それはクラスを構成します。次に、独自のロジックを実行します。

例:オーバーライドOnSelectedItemChangedすると、baseメソッドを呼び出すと、クラスが適切な条件に切り替わり、必要な操作を実行できます(新しく選択した項目で何かを実行します)。

したがって、基本メソッドで何が起こっているかを知ることは有用です。多分あなたはそれを呼ぶ必要はありません。

選択方法: DotPeekでクラスをチェックし、baseメソッドを本当に呼び出す必要があるかどうかを確認します。

重要な場合:基本メソッドは変更をオーバーライドできます。そして、あなたは奇妙な行動をするでしょう。

于 2012-04-30T09:33:53.777 に答える
2

考慮すべき1つの特別なケース:Dispose(bool)イディオムを使用している場合は、独自のリソースをクリーンアップした後でbase.Dispose(bool)を呼び出す必要があります。

(これはWindows.Formsに関連していると思います。これは、Dispose(bool)イディオムを使用しているためです)

于 2012-04-30T10:15:06.240 に答える