廃棄

 

背景:

C#では、通常クラスで定義したインスタンス変数はコンストラクタによって初期化を行い、そのインスタンス変数が内部に持つオブジェクトがマネージリソースであれば、そのオブジェクトへの参照が消失しさえすれば、GCにより解放処理が行われるため、プログラマは自ら特別にそのマネージリソースの解放処理を行う必要が無いので、マネージリソースのインスタンス変数を解放する処理を実装する必要は無い。しかし、アンマネージリソースの解放処理はGCによって行われないため、プログラマ自らが解放処理を行わないとそのアンマネージリソースを使用していたクラスへの参照が消失したとしても、アプリケーションが終了するまではアンマネージリソースが占有していたメモリ領域がアプリケーションプロセスから解放されない。よってこの解放処理はプログラマにより明示的に実装される必要がある。

 

ファイナライザ:

.NETではC#のデストラクタ、VBFinalizeメソッドのことをあわせて「ファイナライザ」と呼ぶ。.NETでは、クラスのインスタンス変数がアンマネージリソースの場合、その解放処理をファイナライザに実装することが必要とされている。

 

Dispose

一方、一般にリソースは(マネージリソース、アンマネージリソースの別なく)不要になったら直ちに解放した方が良い。特にアンマネージリソースを内部に抱えるクラスは、そのリソースの解放処理を明示的に実施するメソッドを備えるべきであり、C#ではこれをIDisposableインターフェィスに備わるDisposeメソッドに明示的に実装することを標準のデザインパターンとして推奨している。Disposeメソッドは、所有するすべてのリソースを解放する必要がある。

 

Dispose/Finalizeパターン:

これらDisposeメソッドとFinalizer.NETの標準的手法に則って実装するパターンのことを、「Dispose/Finalizeパターン」と呼ぶ。以下ポイントを列挙する。

 

実装すべき項目:

     IDisposeインターフェイス

     disposedプライベートインスタンス変数

     Disposeメソッド

     GC.SuppressFinalize(this)処理呼び出し

     Dispose仮想プロテクトメソッドオーバーロード(disposing引数あり)

     ファイナライザ

     ObjectDisposedExceptionスロー処理(Dispose済みリソースを取扱おうとするメソッドを呼び出そうとした場合)

以下、順を追って説明する。

 

// 既定クラスのデザインパターン

public class Base : IDisposable

{

    private bool disposed = false;

 

    // IDisposableを実装する

    public void Dispose()

    {

        // マネージリソースとアンマネージリソースの両方を解放する

        Dispose(true);

        // 以後、このクラスのGCによる呼び出しを抑止する

        GC.SuppressFinalize(this);

    }

 

    // マネージリソースは、クライアントが明示的にDisposeを呼び出したとき(disposing=true)のみ解放する

    // アンマネージリソースは上記局面が明示的に発生しないまま、デストラクタが呼び出されたときであっても解放されるようにする

    // どちらの場合でも、以後disposeフラグにはtrueが設定され、その後(2回目以降)のDispose呼び出しは

    // 何も行わない(リソースへのアクセスを行わず、例外を投げることも無い)

    protected virtual void Dispose(bool disposing)

    {

        if (disposed)

        {

            return;

        }

 

        if (disposing)

        {

            // マネージリソースの解放処理をここに記述する

        }

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

        // disposedフラグをONにする

        disposed = true;

    }

 

    // C#のデストラクタ構文を用いて、ファイナライザを実装する

    ~Base()

    {

        // アンマネージリソースのみのための解放処理を呼び出す

        Dispose(false);

    }

 

    // 保持しているリソースを使って何らかの操作を行うメソッドと仮定

    public virtual string SomeOperation()

    {

        // 既にリソースが破棄されているかチェックする

        ThrowIfDisposed();

 

        return "DisposableBase operation result";

    }

 

    // 既にリソースが破棄されているかチェックして、ObjectDisposedExceptionをスローするためのメソッド

    // disposedprivate変数のため、その値をチェックするこのメソッドのスコープもprivateとした

    // 同様の処理が派生クラスでも必要となる場合、派生クラスでも個別に実装する

    private void ThrowIfDisposed()

    {

        if (disposed) throw new ObjectDisposedException(GetType().FullName);

    }

}

 

// 派生クラスのデザインパターン

public class Derived : Base

{

    private bool disposed = false;

 

    protected override void Dispose(bool disposing)

    {

        if (disposed)

        {

            return;

        }

        try

        {

            if (disposing)

            {

                // マネージリソースの解放処理をここに記述する

            }

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

            // disposedフラグをONにする

            disposed = true;

        }

        finally

        {

            // 基底クラスの解放処理が必ず呼び出されるように、finally句の中から呼び出す

            base.Dispose(disposing);

        }

    }

 

    //派生クラスはデストラクタと引数なしのDisposeメソッドを実装していない。

    //これは、派生クラスが基底クラスのそれらメソッドを継承しているからであり、

    //この実装パターンにしたがっている限り、Dispose(disposing)の中から、

    //base.Dispose(disposing)が呼ばれているので、実際の解放処理が呼び漏らされることはない。

    //※ただし、これはDispose/Finalizeパターンでは、派生クラスにデストラクタを実装してはいけないということではない。

}

 

Disposeメソッド名のカスタマイズ:

クラスの慣習に従って、終了処理メソッドの名称をCloseにする必要がある場合、DisposeメソッドをCloseメソッド内部から呼ぶようにするのはよいやり方である。

 

デストラクタ実装のガイドライン:

     デストラクタの実行は、GCの実行によるパフォーマンスの劣化が想定されるため、アンマネージリソースの解放処理を必要とするクラス以外には実装するべきではない。

     デストラクタが必要な場合、それはアンマネージリソースの解放処理を必要とする場合であり、クラスの利用者による明示的なリソースの解放のためにIDisposableを実装することを検討する必要がある。

     デストラクタは、クラス解放時にCLRにより派生クラス->基本クラスの順で呼ばれるため、publicとしない。(privateとする)

     デストラクタは、そのオブジェクトが保持するアンマネージリソースを全て解放する必要がある。

     デストラクタは、別のオブジェクトが保持するリソースへの参照を持ってはならず、また例外を投げてはならない。

 

Dispose実装のガイドライン:

     Disposeは明示的に解放する必要があるアンマネージリソースをカプセル化するクラスに対して実装する。

     一度Disposeが呼び出された場合、GC.SuppressFinalize(this)を呼び出すことにより、以後このクラスをGCによるファイナライザ呼び出しの対象外とする。これは、GCによるFinalizeの呼び出しがパフォーマンスの観点から非常に高価なため。

     IDisposableを実装する基本クラスの派生クラスは、自身のDisposeメソッドの中で、基本クラスのDisposeメソッドを呼び出す必要がある。

     Disposeがクラス利用者により必ず呼び出されることを前提にはせず、仮にそれが忘れられたとしてもアンマネージリソースが確実に解放されるよう、デストラクタを正しく実装しておく必要がある。(デストラクタ内から引数付きのDispose(false)メソッドを呼び出す)

     すでにDisposeが呼び出された後に、外部からそのクラスのインスタンスにアクセスが発生した場合、クラスはObjectDisposedExceptionを発生させるようにする。ただし、Disposeメソッドは例外をスローせずに複数回呼び出せるように実装されていなければならないため、この例外を投げてはならない。