C#::メモリーリークとファイナライザとIDisposable

イベントを縦横無尽に割り当てたり、データキューだとかリストだとかにオブジェクトをガシガシ突っ込んだりしていると、うっかりメモリリークすることがある。GCありの言語であるC#にとってのメモリリークとは、GCの対象にならないまま忘れ去られることですよ、念のため。「GCがあるから」と思考停止して作りっぱなし投げっぱなしではダメダメなのです。

こいつメモリリークしてるんじゃねーの?と思ったときは、とりあえず怪しいクラスにファイナライザを定義してみる。

class ClassA{
  ~ClassA(){ //ファイナライザ
     Console.WriteLine(GetType().Name + "は無事天国へ旅立ちました。");
  }
}

ファイナライザはGCされるタイミングで実行される。あとは、プログラムと全然関係ないウィンドウを新規で作って、そのウィンドウにボタンでも配置してクリックしたらGC.Collect()を呼ぶようにでもしておけば、任意のタイミングでファイナライズにチャレンジできてハッピーですね。もう開放したと思うタイミング以降、GC.Collect()までにファイナライザのメッセージが出力されなければリークしている(開放できていない)わけです。
ただし、IDisposableなクラスの場合、Dispose()の中でGC.SuppressFinalize()が呼ばれていてファイナライザが実行されないというのはよくある話で(ファイナライザの呼び出しはコストが高い(ホントか?)上にタイミングが読めないから、明示的自滅処理(Dispose)があるならファイナライズは常識的には不要)、そういうクラスは捨てるとき必ずDispose()を呼ぶことになっているはずなので、Dispose()をオーバーライドして同じようにメッセージを仕込んでやる必要があろうかと思います。
ということは、C++のデストラクタと同じように、sealedでないIDisposableクラスのDispose()はvirtualにしろよ、というルールが必要なのかもしれません。

ちなみに、Disposeするときにイベントを含むデリゲート型の変数の初期化を忘れていた!というのはよくある話なので、それも頭の隅にでも置いておくとよいです。また、オーバーライドしたDispose()の中では必ずbase.Dispose()を呼ぶようにしましょう。たとえ自分が作ったクラスであっても親クラスは魔物です。何を抱え込んでいるのかなんて誰にもわからないのです。