Effective C# 3rd 読書メモ 17 標準的なDisposeパターンを実装する

ファイルやDB接続等、.NET Frameworkの管理下にないリソース(unmanaged resource)を利用する際は、適切な後処理を行う必要がある。.NET Frameworkにおいて標準的に利用されるパターンは、IDisposableインタフェースの実装である。

リソースの後処理は、原則Disposeメソッドで行う。クラスの利用者がDisposeを呼び忘れた場合に備えて、ファイナライザでもunmanaged resourceの後処理を行うのが安全である。また、Disposeとファイナライザは、いずれも実際のリソース解放処理を仮想メソッドとして定義して、派生クラスが必要に応じて振る舞いを変更できるようにするとよい。

派生クラスでは、必要に応じてリソース処理の仮想メソッドをオーバーライドする。さらに、自分自身のメンバーフィールドがunmanaged resourceである場合に限って、ファイナライザを実装する。いずれの場合でも、ベースクラスのリソース解放メソッドを呼ぶことを忘れないように。

IDisposable.Dispose()メソッドの実装は、4つの仕事をする責務を負う。

  1. 全てのunmanaged resourceを解放する
  2. 全てのmanaged resourceを解放する(フックしないイベントも含む)
  3. オブジェクトがDispose済みであることを示すフラグをセットする。なお、publicメンバーは、Dispose後に呼ばれた場合には、このフラグを確認して、ObjectDisposed例外を投げる必要がある
  4. ファイナライザの実行を抑制する。これにはGC.SuppressFinalize(this)が利用できる。

IDisposableの典型的な実装は以下のようになる。

public class MyResource : IDisposable
{
    // Dispose済みか否かのフラグ
    private bool _disposed;

    // IDisposableの実装
    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    // 実際の破棄処理を行う仮想メソッド
    protected virtual void Dispose(bool isDisposing)
    {
        if (_disposed)
            return;

        if (isDisposing)
        {
            // managed resourceはDispose時のみ解放する
        }

        // unmanaged resourceは常に解放する
        _disposed = true;
    }

    public void ExampleMethod()
    {
        if (_disposed)
            throw new ObjectDisposedException(nameof(MyResource));
    }
}

public class DerivedResource : MyResource
{
    // Dispose済みフラグはクラスごとにもつ
    private bool _disposed;

    protected override void Dispose(bool isDisposing)
    {
        if (_disposed)
            return;

        if (isDisposing)
        {
            // managed resourceの解放
        }

        // unmanaged resourceの解放

        base.Dispose(isDisposing);

        _disposed = true;
    }
}

なお、ファイナライザは、unmanaged resourceを解放する必要がある場合にのみ定義すべきである。GCは、ファイナライザの定義されたオブジェクトを、ファイナライザ実行待ちのキューに入れ、その後ファイナライザを実行してからオブジェクトを破棄する。そのため、ファイナライザを定義すると、ファイナライザがない場合よりもオブジェクトの生存期間が長くなり、パフォーマンス上の悪影響がある。

また、Disposeでは、リソースの解放以外の処理を実行してはいけない。

参考:IDisposable インターフェイス

コメントをどうぞ

コメントを残す