PG日誌

読者です 読者をやめる 読者になる 読者になる

PG日誌

主にc#の事を書いています

Disposeパターンを実装する

c#

f:id:Takachan:20170112223259j:plain:h200

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

Disposeパターン

Disposeパターンですが、c#で基底クラスが

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

場合、クラスにIDisposeメソッドを付けてリソース解放を明示的に行いたい場合に使います。特に、基底クラスのDispose呼び出しによって派生クラスの資源解放を確実に行いたい場合これを適用します。

MSDNにはDiposeパターンの解説があるのですが日本語の方は機械翻訳で読むのが大変です。(もし日本語版を読むなら2010とか古いバージョンの方が良さそうです。)

コード例

早速コード例ですが、

基底クラスの実装

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

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

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

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

        if(disposing)
        {
            // managed resource の解放処理を記述
        }

        // unmanaged resource の解放処理を記述

        // 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)
        {
            // managed resource の解放処理を記述
        }

        // unmanaged resource の解放処理を記述

        // 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 を忘れない

となります。全ての派生クラスでDisposeを継承せずに基底クラスのIDisposableを有効に使用することを意図しています。

参考資料

このことが書いてある書籍ってほとんどないのですが知っている中で唯一この本にだけDisposeについて言及があります。よかったら読んでみてください。

Effective C# 4.0

Effective C# 4.0

広告を非表示にする