次の方法で共有


破棄パターン

このコンテンツは、 フレームワーク設計ガイドライン (再利用可能な .NET ライブラリの規則、イディオム、パターン、第 2 版) から、Pearson Education, Inc. のアクセス許可によって再印刷されます。 そのエディションは2008年に出版され、その後 、本は第3版で完全に改訂されています。 このページの情報の一部が古くなっている可能性があります。

すべてのプログラムは、実行中に 1 つ以上のシステム リソース (メモリ、システム ハンドル、データベース接続など) を取得します。 開発者は、このようなシステム リソースを使用する場合は、取得して使用した後に解放する必要があるため、注意する必要があります。

CLR では、自動メモリ管理がサポートされます。 マネージド メモリ (C# 演算子 newを使用して割り当てられたメモリ) を明示的に解放する必要はありません。 ガベージコレクタ (GC) によって自動的に解放されます。 これにより、開発者は、メモリを解放するという面倒で困難な作業から解放され、.NET Framework によって提供される前例のない生産性の主な理由の 1 つとなっています。

残念ながら、マネージド メモリは、さまざまな種類のシステム リソースの 1 つに過ぎません。 マネージド メモリ以外のリソースは引き続き明示的に解放する必要があり、アンマネージド リソースと呼ばれます。 GC は特に、このようなアンマネージ リソースを管理するように設計されていません。つまり、アンマネージ リソースを管理する責任は開発者の手にあります。

CLR には、アンマネージ リソースの解放に関するいくつかのヘルプが用意されています。 System.Object は、オブジェクトのメモリが GC によって解放される前に GC によって呼び出される仮想メソッド Finalize (ファイナライザーとも呼ばれます) を宣言し、アンマネージ リソースを解放するためにオーバーライドできます。 ファイナライザーをオーバーライドする型は、ファイナライズ可能な型と呼ばれます。

ファイナライザーは一部のクリーンアップ シナリオで有効ですが、次の 2 つの重要な欠点があります。

  • オブジェクトがコレクションの対象であることを GC が検出すると、ファイナライザーが呼び出されます。 これは、リソースが不要になった後、ある程度の不確定な期間に発生します。 開発者がリソースを解放できる場合、またはリソースがファイナライザーによって実際に解放されるまでの遅延は、多くの希少なリソース (簡単に使い果たすことができるリソース) を取得するプログラムや、リソースの使用を維持するためにコストがかかる場合 (たとえば、大規模なアンマネージ メモリ バッファー) では許容できない場合があります。

  • CLR がファイナライザーを呼び出す必要がある場合は、次のガベージ コレクション (ファイナライザーがコレクション間で実行される) まで、オブジェクトのメモリのコレクションを延期する必要があります。 つまり、オブジェクトのメモリ (および参照するすべてのオブジェクト) は、長期間解放されません。

そのため、アンマネージド リソースをできるだけ早く回収することが重要な場合や、リソースが不足している場合、または最終処理の追加された GC オーバーヘッドが許容できない高パフォーマンスのシナリオでは、ファイナライザーのみに依存することは、多くのシナリオでは適さない場合があります。

フレームワークには、アンマネージド リソースを必要とせずすぐに解放する手動の方法を開発者に提供するために実装する必要がある System.IDisposable インターフェイスが用意されています。 また、オブジェクトが手動で破棄され、終了する必要がなくなったことを GC に伝えることができる GC.SuppressFinalize メソッドも提供されます。この場合、オブジェクトのメモリは以前に再利用できます。 IDisposable インターフェイスを実装する型は、破棄可能な型と呼ばれます。

Dispose パターンは、ファイナライザーと IDisposable インターフェイスの使用方法と実装を標準化することを目的としています。

パターンの主な動機は、 FinalizeDispose メソッドの実装の複雑さを軽減することです。 複雑さは、メソッドが一部のコード パスを共有しているが、すべてのコード パスを共有しないという事実に由来します (相違点については、この章の後半で説明します)。 さらに、決定論的リソース管理の言語サポートの進化に関連するパターンの一部の要素には、歴史的な理由があります。

破棄可能な型のインスタンスを含む型に基本破棄パターンを実装します。 基本パターンの詳細については、「 基本破棄パターン 」セクションを参照してください。

型が他の破棄可能なオブジェクトの有効期間を管理する場合、開発者自身がそれらを破棄する方法を持つ必要があります。 コンテナーの Dispose メソッドを使用すると、これを可能にする便利な方法です。

基本的な Dispose パターンを実装し、ファイナライザーがなく、明示的に解放する必要があるリソースを保持する型にはファイナライザーを提供してください。

たとえば、アンマネージ メモリ バッファーを格納する型にパターンを実装する必要があります。 Finalizable Types セクションでは、ファイナライザーの実装に関連するガイドラインについて説明します。

✓ アンマネージ リソースや破棄可能なオブジェクトを保持せず、サブタイプを持つ可能性が高いクラスに Basic Dispose パターンを実装することを検討してください

この優れた例は、 System.IO.Stream クラスです。 リソースを保持しない抽象基底クラスですが、そのサブクラスのほとんどはそうであり、このため、このパターンを実装します。

基本的な破棄パターン

このパターンの基本的な実装では、System.IDisposable インターフェイスを実装し、Dispose(bool) メソッドとオプションのファイナライザーの間で共有されるすべてのリソース クリーンアップ ロジックを実装するDispose メソッドを宣言します。

次の例は、基本的なパターンの簡単な実装を示しています。

public class DisposableResourceHolder : IDisposable {

    private SafeHandle resource; // handle to a resource

    public DisposableResourceHolder() {
        this.resource = ... // allocates the resource
    }

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

    protected virtual void Dispose(bool disposing) {
        if (disposing) {
            if (resource!= null) resource.Dispose();
        }
    }
}

Boolean パラメーター disposing は、メソッドが IDisposable.Dispose 実装から呼び出されたか、ファイナライザーから呼び出されたかを示します。 Dispose(bool)実装では、他の参照オブジェクト (前のサンプルのリソース フィールドなど) にアクセスする前に、パラメーターを確認する必要があります。 このようなオブジェクトには、メソッドが IDisposable.Dispose 実装から呼び出された場合にのみアクセスする必要があります ( disposing パラメーターが true の場合)。 ファイナライザーからメソッドが呼び出された場合 (disposing が false の場合)、他のオブジェクトにアクセスすることはできません。 その理由は、オブジェクトが予測できない順序で最終処理されるため、オブジェクトまたは依存関係のいずれかが既に終了している可能性があるためです。

また、このセクションは、Dispose パターンをまだ実装していないベースを持つクラスにも適用されます。 パターンを既に実装しているクラスから継承する場合は、 Dispose(bool) メソッドをオーバーライドして、追加のリソース クリーンアップ ロジックを提供するだけです。

✓ DO アンマネージ リソースの解放に関連するすべてのロジックを一元化する protected virtual void Dispose(bool disposing) メソッドを必ず宣言してください。

このメソッドでは、すべてのリソースクリーンアップが行われる必要があります。 このメソッドは、ファイナライザーと IDisposable.Dispose メソッドの両方から呼び出されます。 ファイナライザー内から呼び出される場合、パラメーターは false になります。 ファイナライズ中に実行されているコードが他のファイナライズ可能なオブジェクトにアクセスしないようにするために使用する必要があります。 ファイナライザーの実装の詳細については、次のセクションで説明します。

protected virtual void Dispose(bool disposing) {
    if (disposing) {
        if (resource!= null) resource.Dispose();
    }
}

IDisposable インターフェイスを実装するには、 Dispose(true) を呼び出し、その後に GC.SuppressFinalize(this)を呼び出します。

SuppressFinalizeの呼び出しは、Dispose(true)が正常に実行された場合にのみ発生します。

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

X は 、パラメーターなしの Dispose メソッドを仮想にしません。

Dispose(bool)メソッドは、サブクラスによってオーバーライドされる必要があるメソッドです。

// bad design
public class DisposableResourceHolder : IDisposable {
    public virtual void Dispose() { ... }
    protected virtual void Dispose(bool disposing) { ... }
}

// good design
public class DisposableResourceHolder : IDisposable {
    public void Dispose() { ... }
    protected virtual void Dispose(bool disposing) { ... }
}

X はDisposeおよびDispose()以外のDispose(bool) メソッドのオーバーロードを宣言しません。

Dispose このパターンを体系化し、実装者、ユーザー、コンパイラ間の混乱を防ぐために、予約語と見なす必要があります。 一部の言語では、特定の型に対してこのパターンを自動的に実装することを選択する場合があります。

✓ doDispose(bool) メソッドを複数回呼び出せるようにします。 メソッドは、最初の呼び出しの後に何も行わない場合があります。

public class DisposableResourceHolder : IDisposable {

    bool disposed = false;

    protected virtual void Dispose(bool disposing) {
        if (disposed) return;
        // cleanup
        ...
        disposed = true;
    }
}

X は 、包含プロセスが破損している重大な状況 (リーク、共有状態の不整合など) を除き、 Dispose(bool) 内から例外をスローすることを回避します。

ユーザーは、 Dispose の呼び出しで例外が発生しないことを期待しています。

Dispose例外が発生する可能性がある場合、さらに finally-block クリーンアップ ロジックは実行されません。 これを回避するには、ユーザーは (finally ブロック内で) Dispose のすべての呼び出しを try ブロックにラップする必要があります。これにより、非常に複雑なクリーンアップ ハンドラーが生成されます。 Dispose(bool disposing)メソッドを実行する際、破棄が false の場合には例外を投げないようにしてください。 これにより、ファイナライザー コンテキスト内で実行するとプロセスが終了します。

✓ オブジェクト が破棄された後に使用できないメンバーから ObjectDisposedException をスローします。

public class DisposableResourceHolder : IDisposable {
    bool disposed = false;
    SafeHandle resource; // handle to a resource

    public void DoSomething() {
        if (disposed) throw new ObjectDisposedException(...);
        // now call some native methods using the resource
        ...
    }
    protected virtual void Dispose(bool disposing) {
        if (disposed) return;
        // cleanup
        ...
        disposed = true;
    }
}

✓ 検討すること: Close()に加えて、Dispose()の方法を提供してください。なお、「close」がその分野の標準用語である場合に限ります。

その場合は、 Close の実装を Dispose と同じにし、 IDisposable.Dispose メソッドを明示的に実装することを検討することが重要です。

public class Stream : IDisposable {
    IDisposable.Dispose() {
        Close();
    }
    public void Close() {
        Dispose(true);
        GC.SuppressFinalize(this);
    }
}

Finalizable 型

ファイナライズ可能な型は、ファイナライザーをオーバーライドし、 Dispose(bool) メソッドで最終処理コード パスを指定することによって、基本 Dispose パターンを拡張する型です。

ファイナライザーは正しく実装するのが難しいことで知られます。主に、システムの実行中にシステムの状態に関する特定の (通常は有効な) 仮定を行うことができないためです。 以下のガイドラインを慎重に検討する必要があります。

ガイドラインの一部は、 Finalize メソッドだけでなく、ファイナライザーから呼び出されたコードにも適用されることに注意してください。 以前に定義した基本破棄パターンの場合、これは、Dispose(bool disposing) パラメーターが false の場合にdisposing内で実行されるロジックを意味します。

基底クラスが既にファイナライズ可能で、Basic Dispose パターンを実装している場合は、 Finalize を再度オーバーライドしないでください。 代わりに、 Dispose(bool) メソッドをオーバーライドして、追加のリソース クリーンアップ ロジックを提供する必要があります。

次のコードは、finalizable 型の例を示しています。

public class ComplexResourceHolder : IDisposable {

    private IntPtr buffer; // unmanaged memory buffer
    private SafeHandle resource; // disposable handle to a resource

    public ComplexResourceHolder() {
        this.buffer = ... // allocates memory
        this.resource = ... // allocates the resource
    }

    protected virtual void Dispose(bool disposing) {
        ReleaseBuffer(buffer); // release unmanaged memory
        if (disposing) { // release other disposable objects
            if (resource!= null) resource.Dispose();
        }
    }

    ~ComplexResourceHolder() {
        Dispose(false);
    }

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

X AVOID を使用すると、型の最終化が可能になります。

ファイナライザーが必要と思われる場合は、慎重に検討してください。 パフォーマンスとコードの複雑さの両方の観点から、ファイナライザーを持つインスタンスに関連する実際のコストがあります。 SafeHandleなどのリソース ラッパーを使用して、可能な限りアンマネージ リソースをカプセル化することを優先します。この場合、ラッパーが独自のリソース クリーンアップを担当するため、ファイナライザーは不要になります。

X 値 型を終了処理可能にしないでください。

CLR によって実際に最終処理されるのは参照型のみであるため、値型にファイナライザーを配置しようとすると無視されます。 C# および C++ コンパイラでは、この規則が適用されます。

型が独自のファイナライザーを持たないアンマネージ リソースを解放する責任がある場合、その型をファイナライズ可能にしてください。

ファイナライザーを実装するときは、 Dispose(false) を呼び出し、すべてのリソース クリーンアップ ロジックを Dispose(bool disposing) メソッド内に配置するだけです。

public class ComplexResourceHolder : IDisposable {

    ~ComplexResourceHolder() {
        Dispose(false);
    }

    protected virtual void Dispose(bool disposing) {
        ...
    }
}

✓ DO すべてのファイナライズ可能なオブジェクトに基本的な破棄パターンを実装してください。

これにより、型のユーザーは、ファイナライザーが担当するのと同じリソースの決定論的クリーンアップを明示的に実行する手段が提供されます。

X は 、ファイナライザー コード パス内のファイナライズ可能なオブジェクトにアクセスしないでください。これは、ファイナライズ済みになる重大なリスクがあるためです。

たとえば、別のファイナライズ可能オブジェクト B への参照を持つファイナライズ可能なオブジェクト A は、確実に A のファイナライザーで B を使用することはできません。また、その逆も同様です。 ファイナライザーはランダムな順序で呼び出されます(クリティカルなファイナライゼーションのための弱い順序保証がない限り)。

また、静的変数に格納されているオブジェクトは、アプリケーション ドメインのアンロード中またはプロセスの終了時に特定のポイントで収集されることに注意してください。 finalizable オブジェクトを参照する静的変数にアクセスする (または静的変数に格納されている値を使用する静的メソッドを呼び出す) 場合、 Environment.HasShutdownStarted が true を返した場合、安全でない可能性があります。

Finalize メソッドをprotectedにする。

C#、C++、および VB.NET 開発者は、コンパイラがこのガイドラインを適用するのに役立つため、このことを心配する必要はありません。

X DO NOT では、システム クリティカルなエラーを除き、ファイナライザー ロジックから例外をエスケープできません。

ファイナライザーから例外がスローされた場合、CLR はプロセス全体 (.NET Framework バージョン 2.0 以降) をシャットダウンし、他のファイナライザーが実行されず、リソースが制御された方法で解放されるのを防ぎます。

✓ 強制アプリケーション ドメインのアンロードとスレッドの中止が発生した場合でも、ファイナライザーが絶対に実行する必要がある状況では、重要なファイナライズ可能なオブジェクト (を含む型階層を持つ型) を作成して使用CriticalFinalizerObjectしてください。

Portions © 2005, 2009 Microsoft Corporation. 無断転載を禁じます。

フレームワーク設計ガイドライン:再利用可能な .NET ライブラリの規則、イディオム、パターン、Krzysztof Cwalina および Brad Abrams による第 2 版は、2008 年 10 月 22 日に Microsoft Windows 開発シリーズの一部として Addison-Wesley Professional によって公開されました。

こちらも参照ください