web-dev-qa-db-ja.com

デストラクタでイベントハンドラを削除する必要がありますか?

ランタイム中にアプリケーション内で作成および破棄されるUserControlsを使用します(内部でこれらのコントロールを使用してサブウィンドウを作成および閉じます)。
これはWPF UserControlであり、_System.Windows.Controls.UserControl_を継承しています。オーバーライドできるDispose()メソッドはありません。
PPMMは、私のアプリケーションと同じ存続期間を持つSingletonです。
(WPF)UserControlのコンストラクターで、イベントハンドラーを追加します。

_public MyControl()
{
    InitializeComponent();

    // hook up to an event
    PPMM.FactorChanged += new ppmmEventHandler(PPMM_FactorChanged);
}
_

デストラクタでそのようなイベントハンドラを削除することに慣れました:

_~MyControl()
{
    // hook off of the event
    PPMM.FactorChanged -= new ppmmEventHandler(PPMM_FactorChanged);
}
_

今日、私はこれにつまずいて、疑問に思いました:

1)これは必要ですか?または、GCがそれを処理しますか?

2)これでも機能しますか?または、新しく作成されたppmmEventHandlerを保存する必要がありますか?

あなたの答えを楽しみにしています。

51
Martin Hennings

PPMMは長命オブジェクト(シングルトン)であるため、このコードはあまり意味がありません。

ここでの問題は、そのイベントハンドラがオブジェクトを参照している限り、ガベージコレクションの対象にならないことです。イベントを所有するオブジェクトは生きています。

そのため、デストラクタに何かを入れるのは無意味です。

  1. イベントハンドラは既に削除されているため、オブジェクトはガベージコレクションの対象になりました。
  2. イベントハンドラは削除されず、所有するオブジェクトはガベージコレクションの対象ではないため、ファイナライザは呼び出されません。
  3. 両方のオブジェクトはガベージコレクションに適格です。その場合、ファイナライザでその他のオブジェクトにアクセスするべきではありません。その内部状態がわからないからです。

要するに、これをしないでください

さて、Disposeを実装しているときに、そのようなコードをIDisposableメソッドに追加することについて、別の引数を言うことができます。 thatの場合、Disposeを呼び出しているユーザーコードは、事前定義され制御されたポイントであるため、完全に意味があります。

ただし、ファイナライザ(デストラクタ)は、オブジェクトがガベージコレクションの対象であり、ファイナライザを持っている場合にのみ呼び出されます。

質問はnbr。 2、私は「そのようなイベントから退会できますか?」サブスクライブに使用したデリゲートを保持する必要があるのは、匿名メソッドまたはラムダ式を中心にデリゲートを構築しているときだけです。既存のメソッドを中心に構築すると、機能します。


編集:WPF。そのタグは見えませんでした。申し訳ありませんが、私の答えの残りの部分はWPFにとってあまり意味がありません。また、WPFの第一人者ではないので、本当に言えません。ただし、これを修正する方法があります。ここでは、SOで、他の回答の内容を改善することができれば、それを密かに入手することができます。私の答えの最初のセクションとWPFの関連ビットを追加します。

編集:ここにあるコメントの質問にも答えさせてください。

問題のクラスはユーザーコントロールであるため、その有効期間はフォームに関連付けられます。フォームが閉じられると、フォームが所有するすべての子コントロールを破棄します。つまり、既にDisposeメソッドが存在します

ユーザーコントロールが独自のイベントを管理している場合、これを処理する正しい方法は、Disposeメソッドでイベントハンドラーをアンフックすることです。

(休憩を外した)

WPFはIDisposableをサポートしていません。クリーンアップが必要なWPFコントロールを実装する場合は、代わりに Loaded および Unloaded イベントへのフックを検討する必要があります(または加えて)。

つまりLoadedハンドラーでイベントに接続し、Unloadedハンドラーで切断します。もちろん、これは、コントロールが「ロード」されていない間にイベントを受信する必要がなく、多くのロード/アンロードサイクルを正しくサポートできる場合のみのオプションです。

Loaded/Unloadedイベントを使用する利点は、ユーザーコントロールを使用するすべての場所に手動で配置する必要がないことです。ただし、アプリケーションのシャットダウンが開始された後、Unloadedイベントは発生しないことに注意してください。例えば。シャットダウンモードがOnMainWindowCloseの場合、他のウィンドウのUnloadedイベントは発生しません。これは通常、問題ではありません。これは、アプリケーションが終了する前/間に発生する必要があるUnloadedで確実に行うことができないことを意味します。

8
Paul Groke

まず、destructorを使用せず、Dispose()を使用してリソースをクリアすると言います。

第二に、私の意見では、このコードが非常に頻繁に作成され、寿命が短いオブジェクト内にある場合、take careとしてイベントハンドラを自分で削除する方が良いこれはホルダーオブジェクトへのリンクで、prevent[〜#〜] gc [〜#〜] from収集中

よろしく。

7
Tigran

コードがデストラクタに到達した場合、それはもう問題ではありません。

これは、イベントをリッスンしていない場合にのみ破棄されるためです。
イベントをまだリッスンしている場合、破壊されていません。

2
Yochai Timmer

PPMMは、MyControlインスタンスよりも寿命が長い外部のものですか?

その場合、PPMM_FactorChangedは静的メソッドです。ppmmEventHandlerMyControlインスタンスへの参照をライブで保持します。つまり、インスタンスはガベージコレクションの対象にならず、ファイナライザは起動しません。

削除コードのためにppmmEventHandlerを保持する必要はありません。

GCがそれを処理します。イベントは強い参照を保持しますが、親オブジェクト自体でのみ保持します。最終的に、MyControlのみがイベントハンドラーを介して参照を保持するため、GCが参照を収集します。

一方、ファイナライザを使用しますが、これはスクラクタではありません。この悪い習慣のためです。イベントの登録を解除する場合は、IDisposableを検討する必要があります。

2
dowhilefor

イベントハンドラは扱いにくいため、リソースリークを簡単に隠すことができます。ティグランが言うように。 IDisposeableを使用し、デストラクタを忘れます。正しいかどうかを測定することをお勧めします。タスクマネージャーでアプリのメモリ消費量を調べるだけで、数千のウィンドウをロードして閉じてストレステストを少し行うと、リークがあるかどうかがわかります。

1

ファイナライザ/デストラクタでのイベントのサブスクライブ解除が役立つ場合がありますイベントの発行者がサブスクライブ解除がスレッドセーフであることを保証した場合ownイベントからサブスクライブ解除するオブジェクトは役に立たないでしょうが、実行可能なパターンとして、パブリックに面したオブジェクトに、実際に「すべての作業を行う」プライベートオブジェクトへの参照を保持させることができます。そのプライベートオブジェクトにイベントをサブスクライブさせます。プライベートオブジェクトからパブリックオブジェクトへの参照が存在しない場合、パブリックオブジェクトはまだ誰も関心を持たない場合、ファイナライズの対象になります。そのファイナライザは、プライベートオブジェクトに代わってサブスクリプションをキャンセルできます。

残念なことに、このパターンは、イベントがサブスクライブされたコンテキストだけでなく、イベントがサブスクライブされているオブジェクトがスレッドコンテキストからのサブスクライブ解除要求を受け入れることが保証されている場合にのみ機能します。 .NETが「イベント」コントラクトの一部として、すべてのサブスクライブ解除メソッドがスレッドセーフでなければならないことを要求しても、実装に大きな負担はかかりませんでしたが、何らかの理由でMSはそのような要求を課しませんでした。したがって、ファイナライザ/デストラクタがイベントをできるだけ早くサブスクライブ解除する必要があることを発見した場合でも、それを実現するための標準的なメカニズムはありません。

1
supercat

2)これは動作します

1)(アプリ内メッセージングサービスで)グローバルオブジェクトへのイベントハンドラーが解放されず、GCがそのためにオブジェクトを収集できなかった場合がありました。これは通常、まれな状態だと思います-赤いゲートのANTSのようなプロファイラーを使用すると、これがあなたに起こると思う場合、簡単にメモリプロファイリングを行うことができます。

0
Sascha