この混乱については、https://www.spicelogic.com/Blog/net-event-handler-memory-leak-16のブログで説明しています。ここにまとめて、わかりやすいようにします。
参照は、「必要」を意味します:
まず、オブジェクトAがオブジェクトBへの参照を保持している場合、オブジェクトAが機能するにはオブジェクトBが必要であることを理解する必要があります。したがって、ガベージコレクターは、オブジェクトAがメモリ内に存在している限り、オブジェクトBを収集しません。
この部分は開発者にとって明らかなはずです。
+ =右側のオブジェクトの参照を左側のオブジェクトに注入することを意味します。
しかし、混乱はC#+ =演算子に起因します。この演算子は、この演算子の右側が実際に左側のオブジェクトへの参照を注入していることを開発者に明確に伝えていません。
そうすることで、オブジェクトAはオブジェクトBを必要とすると考えますが、あなたの視点からは、オブジェクトAはオブジェクトBが生きているかどうかを気にする必要はありません。オブジェクトAはオブジェクトBが必要であると考えるので、オブジェクトAが生きている限り、オブジェクトAはオブジェクトBをガベージコレクタから保護します。しかし、イベントサブスクライバーオブジェクトに保護を与えたくない場合は、メモリリークが発生したと言えます。
このようなリークは、イベントハンドラを切り離すことで回避できます。
どのように決定するのですか?
ただし、コードベース全体には多数のイベントとイベントハンドラーがあります。それは、イベントハンドラーをどこでも切り離し続ける必要があることを意味しますか?答えは「いいえ」です。そうしなければならなかった場合、コードベースは冗長になって非常に醜くなります。
単純なフローチャートに従って、分離イベントハンドラーが必要かどうかを判断できます。
ほとんどの場合、イベントサブスクライバーオブジェクトはイベントパブリッシャーオブジェクトと同じくらい重要であり、両方が同時に存在することになっています。
心配する必要がないシナリオの例
たとえば、ウィンドウのボタンクリックイベント。
ここで、イベントパブリッシャーはButtonで、イベントサブスクライバーはMainWindowです。そのフローチャートを適用して質問すると、メインウィンドウ(イベントサブスクライバー)はボタン(イベントパブリッシャー)の前に無効になるはずですか?明らかに違いますよね?それでも意味がありません。では、なぜクリックイベントハンドラの分離を心配するのでしょうか。
イベントハンドラの切り離しが必須の場合の例。
サブスクライバーオブジェクトがパブリッシャーオブジェクトの前に無効になるはずの1つの例を示します。たとえば、MainWindowが「SomethingHappened」という名前のイベントを発行し、ボタンクリックでメインウィンドウから子ウィンドウを表示するとします。子ウィンドウは、メインウィンドウのそのイベントをサブスクライブします。
また、子ウィンドウはメインウィンドウのイベントをサブスクライブします。
このコードから、メインウィンドウにボタンがあることを明確に理解できます。そのボタンをクリックすると、子ウィンドウが表示されます。子ウィンドウはメインウィンドウからイベントをリッスンします。何かをした後、ユーザーは子ウィンドウを閉じます。
ここで、私が提供したフローチャートに従って、「子ウィンドウ(イベントサブスクライバー)はイベントパブリッシャー(メインウィンドウ)の前にデッドになるはずですか?答えはYESであるはずです。そうです。それで、イベントハンドラーを切り離します。 。私は通常、ウィンドウのUnloadedイベントからそれを行います。
経験則:ビュー(WPF、WinForm、UWP、Xamarinフォームなど)がViewModelのイベントをサブスクライブする場合は、必ずイベントハンドラーをデタッチすることを忘れないでください。ViewModelは通常、ビューよりも長持ちするためです。そのため、ViewModelが破棄されない場合、そのViewModelのサブスクライブイベントを含むビューはメモリ内にとどまるので、好ましくありません。
メモリプロファイラーを使用した概念の証明。
メモリプロファイラーでコンセプトを検証できない場合は、それほど楽しくありません。この実験では、JetBrain dotMemoryプロファイラーを使用しました。
まず、次のように表示されるMainWindowを実行しました。
次に、メモリのスナップショットを撮りました。次に、ボタンを3回クリックしました。3つの子ウィンドウが表示されました。これらの子ウィンドウをすべて閉じ、dotMemoryプロファイラーのForce GCボタンをクリックして、ガベージコレクターが確実に呼び出されるようにしました。次に、別のメモリスナップショットを撮って比較しました。見よ!私たちの恐れは本当でした。チャイルドウィンドウは、閉じてもガベージコレクターによって収集されませんでした。それだけでなく、ChildWindowオブジェクトのリークされたオブジェクト数も「3」と表示されます(ボタンを3回クリックして3つの子ウィンドウを表示しました)。
では、次のようにイベントハンドラーを切り離しました。
次に、同じ手順を実行し、メモリプロファイラーを確認しました。今回はすごい!これ以上のメモリリークはありません。