アイコン関連のリソースリーク

GDNJで アイコン関連の話題 を見ていて、思い出したので書いておこう。

  • ImageList に Add(Icon) したリソースは Dispose() では回収できない
  • Form.Icon を設定したら、破棄する前に必ず null を代入せよ

ImageList に Add(Icon) したリソースは Dispose() では回収できない

イメージリストは、複数の画像を保持することができる Windows のコントロールで、マスク画像を別途保持することで透明色を表現することもできる。.NET で扱える多くの画像フォーマットは透明色をサポートしているが、透明色をサポートした画像フォーマットが少なかった頃にはこのマスキング機能は有用であったし、リストビューなどの別のコントロールが間接的にイメージリストを要求したり使用したりすることも多い。
これに対してアイコン(とカーソル)も古くから透明(と反転)をサポートしている画像フォーマットであり、イメージリストと扱う画像に類似性がある。 .NET ではアイコンのみが System.Windows.Forms.Icon クラスという独自の実装を持っており、ImageList クラスは System.Drawing.Image を受け取る Add() とは別にアイコンを画像として追加するための Add() メソッドを持つ。
ところが、この Icon を指定した Add() を呼び出した場合、追加したアイコン*1 が Dispose() を呼び出しても破棄されないという問題があります。アイコンクラスは Icon 型で管理されているので、参照を失った後 GC とファイナライザによってリソースの回収は実施されますが、Form の上に ImageList などを貼り付けたダイアログなど、オブジェクトとしての寿命が Dispose() の呼び出し後にも長く続くような場合、長期間 Dispose 済みの ImageList がメモリ上に存在するような場合には注意が必要です。

Form.Icon を設定したら、破棄する前に必ず null を代入せよ

ImageList のアイコンは GC によって回収された後、ファイナライザによって破棄されるというシナリオがありましたが、こちらのアイコンは設定されてしまうとアンマネージドな世界でアイコンが管理されてしまうため、マネージドなオブジェクトと関連性がなく、GC による回収やファイナライザによるリソース開放が発生しないという致命的なものです。*2
まず、Form の Icon プロパティに設定した Icon は、誰が Dispose() を呼び出す必要があるのか、ということから考えておく必要があります。この Icon の Dispose() を呼ぶ責任があるのは、Form のコンシューマである我々開発者です。Form は Icon の所有権を持たず、自身が破棄される場合に Icon の破棄を行いません。このため、2つ以上の Form に同じ Icon のインスタンスを設定することができます。これは逆に、

protected override void Dispose(bool disposing)
{
  if (disposing)
  {
    if (this.components != null) this.components.Dispose();
    if (this.Icon != null) this.Icon.Dispose();
  }
}

などというようなことをしてしまうと、同じアイコンを共有する別のフォームの CreateHandle() において ObjectDisposedException が発生してしまうことになります。*3 解決策は小見出にあるように、

protected override void Dispose(bool disposing)
{
  if (disposing)
  {
    if (this.components != null) this.components.Dispose();
    
    this.Icon = null;
  }
}

とすることです。*4 Form は Icon プロパティに null が設定された場合、ウィンドウをアイコン無しに再設定するため問題となっているアンマネージな領域で管理されたアイコンが上書きされ破棄されるという効果になります。

*1:正確にはそのクローン

*2:アイコンは画像と透明マスクと反転マスクで構成され、GDI リソースとしては HBITMAP を2つ消費します

*3:他にも、アイコンを設定していない状態の Icon プロパティは既定のアイコンを返すため、それを勝手に破棄してはならないなどの問題もあります。

*4:割り当てたアイコンは、別途安全な機会に破棄することになります。