Disposeパターンを実装する

Disposeパターンについて調べたので結果をまとめてみようと思います。

Disposeパターン

Disposeパターンですが、クラスの特徴が

  • アンマネージリソースを使用する
  • 派生クラスで継承される予定がある

場合、クラスにIDisposeメソッドを付けてリソース解放を明示的に行う必要がある場合に使用します。特に、基底クラスのDisposeメソッド呼び出しにり、派生クラス側の資源解放を同時に行いたい場合これを適用します。

コード例

早速コード例です。

// 基底クラスの実装
public class Base : IDisposable
{
    // Disposeされたかどうかを表すフラグ
    //   true : Dispose済み
    //   false : Disposeまだ
    private bool disposed;

    // IDisposableの実装
    public Dispose()
    {
        this.Dispose(true);

        // Dispoaseが呼ばれた場合デストラクタを呼ばない指示
        //  → 2重解放しないようにする
        GC.SuppressFinalize(this);
    }

    ~Base() => this.Dispoase(true); // デストラクタ経由の時は全部開放

    // Disposeパターン用のメソッド
    //
    // disposing
    //   true : Disposeメソッドからの呼び出し
    //   false : デストラクタからの呼び出し
    protected virtual void Dispose(bool disposing)
    {
        if(this.disposed)
        {
            return; // 解放済みなので処理しない
        }

        if(disposing)
        {
            // マネージリソースの解放処理を記述
        }

        // アンマネージリソースの解放処理を記述

        // Dispose済みを記録
        this.disposed = true;
    }
}

派生クラスの実装

public class Derived : Base
{
    // Disposeされたかどうかを表すフラグ
    // (派生クラスにも実装する)
    //   true : Dispose済み
    //   false : Disposeまだ
    private bool disposed = false;

    // Baseクラスのメソッドをoverrideしている
    protected override void Dispose(bool disposing)
    {
        // 基底クラスのつくりと同じ ---->

        if(this.disposed)
        {
            return; // 解放済みなので処理しない
        }

        if(disposing)
        {
            // マネージリソースの解放処理を記述
        }

        /// アンマネージリソースの解放処理を記述

        // Dispose済みを記録
        this.disposed = true;

        // <---- 基底クラスのつくりと同じ

        // 忘れずに、基底クラスのDisposeを呼び出す【重要】
        base.Dispose(disposing);
    }
}

利用側のコード

public class AppMain
{
    public void Main(string[] args)
    {
        // ☆使用例1☆
        Base base_1 = new Derived();
        base_1.Dispose();
        // Derived.Dsipose(bool) → Base.Dispose(bool) の順に呼ばれる

        // ☆使用例2☆
        using(Base base_2 = new Derived())
        {
            // (省略)
        }
        // この時点で base_1 の Dipose と同じ動きになる

        // ☆使用例3☆
        Derived derived = new Derived();
        ((Base)derived).Dipose();
        // これも Derived.Dsipose(bool) → Base.Dispose(bool) の順に呼ばれる

        // ☆使用例4☆
        using(Derived  base_2 = new Derived())
        {
            // (省略)
        }
        // これも同じ
    }
}

もしこのコードを他人に配るような場合、公開メソッドには以下のようにDipose済みだったら例外を投げる処理を実装をした方が幸せになれるかもしれません。

public void Foo()
{
    if(this.disposed)
    {
        throw new InvaliedOperationException("Object is disposed.");
    }
}

注意点

コード中のコメントの通りですが、Dipose(bool disposing)を各々のクラスで実装する必要があり、それぞれ手書きする必要があります。(面倒なので世の中テンプレートを配布されている方も居るのでそれらを使った方が確実です)

また、派生クラス側での注意点以下の通りです。

  • IDisposableを継承しない
  • デストラクタ(Finaliza)を実装しない
  • base.Disposeメソッドの呼び出しを忘れない
  • Dispose(bool)は protected virtual を忘れない

全ての派生クラスでIDisposableを継承せずに基底クラスのDispose経由で呼び出される事を意図しています。

参考資料

読まないとモグリ、Effective C#

Effective C# 4.0

Effective C# 4.0

MSDNの「Diposeパターン」 https://msdn.microsoft.com/ja-jp/library/b1yfkh5e.aspx

Dispose にまつわる余談 - ++C++飛行 https://ufcpp.net/study/csharp/rm_disposable.html