WeakEventパターンだからって安心しきっててRemoveListenerしないと痛い目みる

前回(MVVMパターンで陥りやすいメモリリークについて考えてみた)の続きっぽいこと書きます。

前回ちょっとだけ触れたWeakEventパターンについてもう少し。
※このパターン自体についての詳しい説明はないのでリンク先を見てください。WeakEventでググってきた方すみません。この記事はWeakEventパターンを実装してもオブジェクトの生存期間については意識しとかないと行けないという初歩的なことを書いてます。

僕がこのパターンをはじめに知ったとき

これはすごい!
これで好きなようにイベント購読してもどこから参照残ってるかなんて気にしなくていいじゃん!
もう今度から+=とか−=とか使うのやめようぜ! 全部WeakEventにしようぜ!
さよならメモリリーク! ビバ! WeakEventパターン!

とか思った。

しかし・・・

人生そんなに甘くなかった。
甘いのは俺だった。

なぜか。
WeakEventパターンは、簡単に言ってしまえば
イベントリスナーが生きている場合のみイベントソースはイベントを通知するという仕組みだけど、
この「生きている場合のみ」がクセモノだった。

「生きている場合」ってどうゆうこと?

Garbage CollecterにCollectされていないこと。

え、Collectっていつされるんだったっけ?

当然ですが、自分でGC.Collect()メソッド呼ばなければ、Garbage Collecterが最適な時点を適切に判断して行ってくれます。
ガベージ コレクションの基礎

最適な時点を適切に判断って、国会答弁みたいだな(冗談)
つまるところ、GC.Collect()を呼ばない限りいつ実行されるかはプログラマは判断できないってこと。

では、どういうことが起こるか。

WeakEventパターンだからと安心しきってリスナー登録を解除しないと、タイミングによっては死んだと思っていたリスナーが生きていて、ハンドラーが呼び出されてしまうことがあり得るってこと。

死んだはずだよ、お富さん。。。

死にゆくオブジェクトのプロパティが変わるくらいなら気にしなくて良いのかもしれないけど、例えばデータを更新したりするようなことをハンドラで行っていた場合、タイミングによっては致命的なバグになるんじゃあ・・・。

バグにならないとしてもGarbageがいつCollectされるかが定かではない以上、呼ばれるか呼ばれないかを制御できていないことになるのはちょっと気持ち悪い。
じゃあ毎回GC.Collect()をコードで呼ぶ?・・・それも「ガベージ コレクションの強制実行」のガイドラインに反している気がする・・・。(古いドキュメントだけど有効だと思う)

結局、プログラムの方はリスナーがいつ消えて欲しいかわかるわけ無いんだから、消えて欲しいのなら人間がちゃんと「消えて下さい」って命令してあげなきゃ行けない。
つまり、RemoveListenerメソッドを呼んであげなければならない。
普通に考えると「そりゃそうだ」なんだけど、「WeakEventにしてるからイベントハンドラ解除はしなくても安心」なんて考えちゃってると、大変。
要は強参照だろうが弱参照だろうがイベントハンドラ(リスナー)が必要なくなった時点で解除をしないといけない。
うーん。勉強になった。めでたしめでたし。

ちょと待て!
そもそもWeakEventパターンはいつ登録を解除するかを明示的には認識できない場合に使用するもんじゃないの?
登録を解除する時期がわかってるならWeakEventパターンなぞ使わなくて良いじゃない・・・。

元も子もない結論になりそうだったのでもう1度WeakEventパターンのMSDNライブラリをよーく読む。

引用:
・弱いイベント パターンの実装は、主にコントロール作成者にとって意味があります。コントロールの作成者には、作成したコントロールの動作と格納およびそれを組み込むアプリケーションに与える影響に対する主な責任があります。 これには、コントロール オブジェクトの有効期間の動作 (特に、上に示したメモリ リークの問題への対応) が含まれます。
・弱いイベント パターンの有意義な 1 つの特徴は、(あなたの*1)コード ベースの一部でないイベントに対してパターンを実装できることです。

ふーむ。
このライブラリの記事のかかれ方だと、WeakEventはかなり限定的な局面を想定しているようにも思える。
イベントソースに対して制御できる状況にない、コントロール作者とか、フレームワークのコアな部分とか。
逆に言うと何でもかんでもWeakEventパターンにするんではなく、イベントソースもイベントリスナーも完全に管理できる場合は普通の(強参照)のイベントパターンを使えということかな。
(あとパフォーマンス的にも強参照の方が良いしね)
イベントソースを管理できないコアな場面でWeakEventパターンを使用する場合も、リスナー登録解除の道は実装しておくべきだろうとは思う。
(たとえば、イベントソースが変更されたらきちんとRemoveListenerするとか、CloseやDisposeメソッドという名前で外から解除できるようにしておくとか)

繰り返し強調したいのは、
決してWeakEventパターンが不備なのではなく、WeakEventパターンさえ実装すれば万事OKと考えることがよくないということ。
常にオブジェクトの生存期間については意識しとかなきゃなあということ。
(WeakEventパターンさえ実装すれば万事OKなんて誰も考えていないかもだけど)

ちなみに、イベントに限らず弱参照全般についてはここにこんな記述もあった。
■弱い参照

引用:
弱い参照を使用するに当たってのガイドライン
・メモリ管理の問題の自動的な解決方法として、弱い参照を使用しないでください。 代わりに、アプリケーションのオブジェクトを処理するための効果的なキャッシュ ポリシーを作成します。

まとめ

  • WeakEventパターンもできるだけRemoveListenerすること。
  • いつ登録を解除するかを認識できないイベントはWeakEventパターンで。
  • 認識できるものは+=−=で。

買えるモノはMaster Cardで。

*1:抜けている様だったので補足しました