9

Dispose パターンの使用方法と、そのように機能する理由を説明する優れた回答を読みました。

IDisposable インターフェイスの適切な使用

この投稿では、2 つの異なるシナリオで Dispose パターンを使用することを明確に述べています。

  1. 管理されていないリソースを取り除く (そうしなければならないため)
  2. 管理されたリソースを取り除く (役に立ちたいから)

私の質問は:

  • オブジェクトがその存続期間全体にわたって外部イベントにサブスクライブされている場合、Dispose メソッドでそのイベントから登録を解除することも一般的/良い方法ですか? その目的のために IDisposable インターフェイスを実装しますか?
4

6 に答える 6

7

はい、そうすべきです。

これは、解放する必要がある「リソース」があることをクラスの消費者に示す最良の方法です。(イベント サブスクリプションは技術的にはリソースではありませんが)

于 2012-11-13T14:19:36.793 に答える
2

Dispose多くの (ほとんどの?) ケースでは、オブジェクトは呼び出された直後にガベージ コレクションの対象になります。たとえば、これは常にIDisposableusing ステートメントでインスタンス化されたオブジェクトに当てはまります。

using(var myDisposableObject = ...)
{
    ...
} // myDisposableObject.Dispose() called here

// myDisposableObject is no longer reachable and hence eligible for garbage collection here

この状況では、個人的には、一般的なケースでイベント サブスクリプションを削除してコードを乱雑にすることはありません。

たとえば、ASP.NETPageまたはUserControlisIDisposableであり、多くの場合、Web ページ上の他のコントロールからのイベントを処理します。Pageorが破棄されたときにこれらのイベント サブスクリプションを削除する必要はありませんUserControl。実際、これが行われている ASP.NET アプリケーションを見たことがありません。

アップデート

他の回答者は、クラス のDisposeメソッドで常にイベントのサブスクライブを解除する必要があることを示唆しています。IDisposable

これが適切なアプリケーション固有の状況があるかもしれませんが、一般的なケースではこれに同意しません。

論理的な結論は、イベントにサブスクライブするクラスは、IDisposable決定論的にサブスクライブを解除できるようにする必要があるということです。この推奨事項が管理されていないリソースを所有するクラスにのみ適用されるべきであるという論理的な理由はわかりません。次の理由から、これは一般的な推奨事項ではないと思います。

  • イベントのサブスクライブを解除できるようにするためだけにクラスを作成するIDisposableと、クラスのユーザーにとって複雑さが増します。

  • メソッド内のイベントからサブスクライブを解除Disposeするには、開発者は削除する必要があるイベント サブスクリプションを追跡する必要があります。これは、見逃しやすい (またはメンテナンス開発者が追加する) ため、やや脆弱です。

  • クラスが有効期間の長いパブリッシャーからのイベントをサブスクライブする状況では、弱いイベント パターンを使用して、サブスクライバーの有効期間がイベント サブスクリプションの影響を受けないようにする方がおそらく適切です。

  • 多くの状況 (例: ASP.NET Page クラスがその子コントロールからのイベントをサブスクライブする) では、パブリッシャーとサブスクライバーの有効期間は密接に関連しているため、サブスクライブを解除する必要はありません。

于 2012-11-13T14:42:05.290 に答える
0

私は二面的なアプローチを好む:

(1) 明示的な方法UnregisterFromExternalEvents();

Dispose()(2)そのメソッドへの呼び出し。

このようにして、クラスのインスタンスを制御するコードは、明示的に登録を解除するか、Dispose適切に破棄してそのような問題を処理することを信頼できます。

于 2012-11-13T14:21:06.470 に答える
0

はい、それは非常に良い考えです。イベント パブリッシャーは、イベント サブスクライバーへの参照を保持します。これにより、サブスクライバーがガベージ コレクションされるのを防ぐことができます。(イベント ハンドラーはガベージ コレクションの発生を停止しますか?を参照してください。 )

さらに、イベント ハンドラーが解放するリソースを使用する場合、イベント ハンドラー (イベント発行者によって引き続き呼び出されます) は、リソースが解放された後に例外を生成する可能性があります。

したがって、特に非同期または複数のスレッドを使用している場合は、リソースを解放する前にイベントから登録を解除することが重要です。これは、一部のシナリオでは、リソースを解放してからそのイベントを登録解除するまでの間にイベントが発生する可能性があるためです。 .

次のコードはこれを示しています。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ReleaseEvents
{
    class Program
    {
        public static event EventHandler SomethingHappened;

        static void Main( string[] args )
        {
            using ( var l_dependent = new Dependent() )
            {
                SomethingHappened( null, EventArgs.Empty );
            }

            // Just to prove the point, garbage collection
            // will not clean up the dependent object, even
            // though it has been disposed.
            GC.Collect();

            try
            {
                // This call will cause the disposed object
                // (which is still registered to the event)
                // to throw an exception.
                SomethingHappened( null, EventArgs.Empty );
            }
            catch ( InvalidOperationException e )
            {
                Console.ForegroundColor = ConsoleColor.Red;
                Console.WriteLine( e.ToString() );
            }

            Console.ReadKey( true );
        }
    }

    class Dependent : IDisposable
    {
        private object _resource;

        public Dependent()
        {
            Program.SomethingHappened += Program_SomethingHappened;
            _resource = new object();
        }

        private void Program_SomethingHappened( object sender, EventArgs e )
        {
            if ( _resource == null )
                throw new InvalidOperationException( "Resource cannot be null!" );

            Console.WriteLine( "SomethingHappened processed successfully!" );
        }

        public void Dispose()
        {
            _resource = null;
        }
    }
}

SomethingHappenedイベントが2 回目に発生すると、Dependentクラスは InvalidOperationException をスローします。これを防ぐには、イベントの登録を解除する必要があります。

    class Dependent : IDisposable
    {
        // ...

        public void Dispose()
        {
            Program.SomethingHappened -= Program_SomethingHappened;

            _resource = null;
        }
    }

私が最初に MVVM アーキテクチャを実装しようとしたときに、実際にこの問題に遭遇しました。ViewModel を切り替えていたとき、唯一の参照 (ActiveViewModel プロパティ) だと思っていたものを解放しただけでした。ViewModel がサブスクライブされたイベントがそれをメモリに保持していることに気付きませんでした。アプリケーションの実行時間が長くなるにつれて、ますます遅くなりました。最終的に、私がリリースしたと思っていた ViewModel が、実際にはイベントを処理し続けていたことに気付きました (費用がかかります)。この問題を解決するには、イベント ハンドラーを明示的に解放する必要がありました。

于 2012-11-13T15:14:25.537 に答える
0

はい、すべての外部イベントを登録解除することをお勧めしますが、イベントの疎結合の性質のため、極端に必要というわけではありません。サブスクライバ オブジェクトのイベント エントリ ポイント参照をイベント ジェネレータから削除します。yes が役に立ちます。

disposeメソッドで退会する部分もOKです。Dispose メソッドの経験則は次のとおりです。「Dispose メソッドは、dispose が複数回呼び出された場合でも機能する方法でリソースをアンロードする必要があります。つまり、dispose でリソースを 1 回だけ解放する必要があります。(これには、dispose する前にチェックが必要になります。資力)"

于 2012-11-13T14:32:27.260 に答える