2012/05/18

IDispose 実装のパターン

忘れやすいので書き残しておく。

class Foo: IDisposable
{
 // アンマネージリソース
 private IntPtr unmanaged;

 // IDisposable を実装している他のクラス(このクラスがオーナー)
 private OtherDisposableClass other;

 // イベントを持ってて、このクラスがイベントをサブスクライブしてる
 // このクラスは参照してるだけ
 private ReferencedWithEvent refwe;

 // 自分のイベント
 public event EventHandler AnyEvent;

 // もし、アンマネージリソースをこのクラスが直接管理していなければ、
 // ファイナライザを用意しなくていい。
 ~Foo()
 {
  Dispose(false);
 }

 public void Dispose()
 {
  Dispose(true);
  GC.SupressFinalize(this);
 }

 protected virtual void Dispose(bool disposing)
 {
  if (disposing) {
   // Dispose() から呼び出された時は、保持しているIDisposableメンバを
   // null チェックしてから Dispose() を呼び出して null にセットしておく。
   // 他の例にあるような disposed フラグは使わない。
   // その理由は、other のインスタンスが Open() などのように
   // コンストラクタ外で確保される場合でも同じパターンでコードを書くため。
   // また、null チェックして null 代入しておけば Dispose() が複数回コールされても大丈夫。
   if (other != null) {
    other.Dispose();
    other = null;
   }
   if (refwe != null) {
    // サブスクライブしたイベントを解除
    // More Effective C# 23
    // イベントハンドラの定義宣言は省略...
    refwe.Event -= refwe_Event;
    refwe = null;
   }
  }

  // 直接このクラスが管理しているアンマネージリソースの解放処理をここに書く。
  // 仮に other がアンマネージリソースを保持していることを知っていても、
  // ここで Dispose を呼び出してはいけない。
  // ここはファイナライザからの呼び出しでも実行される場所で、ファイナライザからの
  // 呼び出しである場合、other はすでにファイナライズされているかもしれない。
  // .Net の GC は参照カウントでなくルート探索で動作するので、自分が参照して
  // いるからといって、そのオブジェクトがファイナライズされていない保証はない。
  // したがって、ここでは他のオブジェクトを参照してはいけない。
  // http://msdn.microsoft.com/ja-jp/library/system.idisposable.dispose.aspx

  // もし、IntPtr.Zero も有効な値なのであれば、
  // 別のフラグを用意して、開放したかどうかを管理する。
  if (unmanaged != IntPtr.Zero) {
   UnmanagedResource.Free(unmanaged);
   unmanaged = IntPtr.Zero;
  }

  // 単なる null 代入でいいものはここに書く。
  AnyEvent = null;

  // ObjectDisposedException を実装したければ、ここでフラグ立てるとかする。

  // もし、このクラスが継承クラスなら、
  // base.Dispose(disposing);
 }
}