Dispose メソッドの実装

Dispose メソッドは、主に管理対象外リソースを解放するために実装されます。 IDisposable の実装であるインスタンス メンバーを使用する場合は、Dispose 呼び出しをカスケードするのが一般的です。 Dispose を実装するのは他にも理由があります。たとえば、割り当てられたメモリを解放したり、コレクションに追加された項目を削除したり、取得されていたロックのリリースを通知したりするためです。

.NET のガベージ コレクターは、アンマネージド メモリの割り当てや解放を行いません。 破棄パターンと呼ばれる、オブジェクトを破棄するパターンによって、オブジェクトの有効期間に順番が付けられます。 dispose パターンは、IDisposable インターフェイスを実装するオブジェクトに使用されます。 このパターンは、ガベージ コレクターがアンマネージド オブジェクトを再利用できないため、ファイルおよびパイプ ハンドル、レジストリ ハンドル、待機ハンドル、またはアンマネージド メモリのブロックへのポインターを操作する場合に一般的です。

Dispose メソッドをべき等にする (複数回呼び出し可能など) 必要がある場合でも、例外をスローすることなく呼び出されるようにして、リソースが常に適切にクリーンアップされるようにする必要があります。 さらに、後続の Dispose の呼び出しでは、何も行ってはなりません。

GC.KeepAlive メソッドに用意されているコード例は、オブジェクトまたはそのメンバーへのアンマネージド参照がまだ使用されている間にガベージ コレクションによってファイナライザーが実行される方法を示しています。 現在のルーチンの開始時点からこのメソッドが呼び出される時点まで、GC.KeepAlive を利用してそのオブジェクトをガベージ コレクションの対象から外すことは理にかなっていると考えられます。

ヒント

依存関係の挿入に関して、サービスを IServiceCollection に登録すると、IServiceCollectionがお客様に代り暗黙的に管理されます。 IServiceProvider とそれに対応する IHost によって、リソースのクリーンアップが調整されます。 具体的には、IDisposable および IAsyncDisposable の実装は、それらに指定した有効期間の終了時に適切に破棄されます。

詳細については、「.NET での依存関係の挿入」を参照してください。

セーフ ハンドル

オブジェクトのファイナライザーのコードを記述することは、正しく行わないと問題が発生する可能性がある複雑なタスクです。 そのため、ファイナライザーを実装するのではなく、System.Runtime.InteropServices.SafeHandle オブジェクトを構築することをお勧めします。

System.Runtime.InteropServices.SafeHandle は、アンマネージ リソースを識別する System.IntPtr をラップする抽象マネージド型です。 Windows ではハンドルを、Unix ではファイル記述子を識別します。 SafeHandle が、このリソースが解放されるのを確実に 1 回にするために必要なすべてのロジックを提供するのは、SafeHandle が破棄されるとき、または SafeHandle へのすべての参照が削除され、SafeHandle インスタンスが終了するときのいずれかです。

System.Runtime.InteropServices.SafeHandle は抽象基底クラスです。 派生クラスは、さまざまな種類のハンドルのために特定のインスタンスを提供します。 これらの派生クラスは、System.IntPtr のどの値が無効と見なされるかを検証し、実際にハンドルを解放する方法を検証します。 たとえば、SafeFileHandleSafeHandle から派生し、開いているファイル ハンドル/記述子を識別する IntPtrs をラップし、その SafeHandle.ReleaseHandle() メソッドをオーバーライドして閉じます (Unix では close 関数または Windows では CloseHandle 関数)。 アンマネージ リソースを作成する .NET ライブラリのほとんどの API は、それを SafeHandle でラップし、生ポインターを渡す代わりに、必要に応じて SafeHandle をユーザーに返します。 アンマネージ コンポーネントと対話し、アンマネージ リソースの IntPtr を取得する状況では、独自の SafeHandle 型を作成してラップすることができます。 その結果、ファイナライザーを実装する必要がある SafeHandle 以外の型はほとんどありません。 ほとんどの破棄可能なパターン実装では、最終的に他の管理対象リソースがラップされるだけであり、その一部は SafeHandle オブジェクトの可能性があります。

Microsoft.Win32.SafeHandles 名前空間の次の派生クラスでは、セーフ ハンドルが提供されます。

クラス 保持されるリソース
SafeFileHandle
SafeMemoryMappedFileHandle
SafePipeHandle
ファイル、メモリ マップされたファイル、パイプ
SafeMemoryMappedViewHandle メモリ ビュー
SafeNCryptKeyHandle
SafeNCryptProviderHandle
SafeNCryptSecretHandle
暗号の構成要素
SafeRegistryHandle レジストリ キー
SafeWaitHandle 待機ハンドル

Dispose() と Dispose(bool)

IDisposable インターフェイスでは、パラメーターのない Dispose メソッドを 1 つ実装する必要があります。 また、すべての非シールド クラスには、Dispose(bool) オーバーロード メソッドが必要です。

メソッド シグネチャは次のとおりです。

  • public で非仮想 (Visual Basic では NotOverridable) (IDisposable.Dispose の実装)。
  • protected virtual (Visual Basic では Overridable) の Dispose(bool)

Dispose() メソッド

この public で非仮想 (Visual Basic では NotOverridable)、パラメーターなしの Dispose メソッドは、不要な場合に (型のコンシューマーによって) 呼び出されるため、その目的は、アンマネージド リソースを解放し、通常のクリーンアップを実行し、ファイナライザーが存在する場合、それを実行する必要がないことを示すことです。 マネージド オブジェクトに関連付けられている実際のメモリを解放するのは、常にガベージ コレクターのドメインです。 このため、次のような標準的な実装があります。

public void Dispose()
{
    // Dispose of unmanaged resources.
    Dispose(true);
    // Suppress finalization.
    GC.SuppressFinalize(this);
}
Public Sub Dispose() _
    Implements IDisposable.Dispose
    ' Dispose of unmanaged resources.
    Dispose(True)
    ' Suppress finalization.
    GC.SuppressFinalize(Me)
End Sub

Dispose メソッドはオブジェクトのクリーンアップをすべて実行するため、ガベージ コレクターはもはやオブジェクトの Object.Finalize のオーバーライドを呼び出す必要はありません。 そこで、SuppressFinalize メソッドを呼び出して、ガベージ コレクターによるファイナライザーの実行を抑制します。 型にファイナライザーがない場合、GC.SuppressFinalize を呼び出しても無効です。 実際のクリーンアップは、Dispose(bool) メソッド オーバーロードによって実行されます。

Dispose (bool) メソッドオーバーロード

オーバーロードでは、disposing パラメーターは Boolean で、メソッドの呼び出し元が Dispose メソッドか (値は true)、それともファイナライザーか (値は false) を示します。

protected virtual void Dispose(bool disposing)
{
    if (_disposed)
    {
        return;
    }

    if (disposing)
    {
        // TODO: dispose managed state (managed objects).
    }

    // TODO: free unmanaged resources (unmanaged objects) and override a finalizer below.
    // TODO: set large fields to null.

    _disposed = true;
}
Protected Overridable Sub Dispose(disposing As Boolean)
     If disposed Then Exit Sub	

     ' A block that frees unmanaged resources.
     
     If disposing Then
         ' Deterministic call…
         ' A conditional block that frees managed resources.    	
     End If
     
     disposed = True
End Sub

重要

disposing パラメーターは、ファイナライザーから呼び出されたときは falseIDisposable.Dispose メソッドから呼び出されたときは true にする必要があります。 つまり、確定的に呼び出されたときは true、非確定的に呼び出されたときは false です。

メソッドの本体は 3 つのコード ブロックで構成されます。

  • 条件付き戻りのブロック (オブジェクトが既に破棄されている場合)。

  • アンマネージ リソースを解放するブロック。 このブロックは、disposing パラメーターの値に関係なく実行されます。

  • マネージド リソースを解放する条件付きブロック。 このブロックは、disposing の値が true の場合に実行されます。 解放するマネージド リソースには、次のオブジェクトを含めることができます。

    • IDisposable を実装するマネージド オブジェクト。 条件付きブロックを使用して Dispose の実装を呼び出すことができます (カスケード破棄)。 System.Runtime.InteropServices.SafeHandle の派生クラスを使用してアンマネージ リソースをラップしている場合は、ここで SafeHandle.Dispose() の実装を呼び出す必要があります。

    • 大量のメモリを消費するか、不足しているリソースを消費するマネージド オブジェクト。 null に大きなマネージド オブジェクト参照を割り当てて、到達不能の可能性が高くなるようにします。 こうすると、非確定的に再利用された場合よりも、速く解放されます。

メソッドの呼び出し元がファイナライザーの場合、アンマネージ リソースを解放するコードだけを実行する必要があります。 実装側は、正しくないパスと、廃棄された可能性のあるマネージド オブジェクトとの間でやり取りが発生しないように確認する必要があります。 これが重要なのは、終了処理時にガベージ コレクターによってマネージド オブジェクトが破棄される順序が非確定的であるからです。

カスケード破棄呼び出し

クラスがフィールドまたはプロパティを所有しており、その型が IDisposable を実装する場合、それを含むクラスそのものが IDisposable も実装する必要があります。 IDisposable の実装をインスタンス化して、それをインスタンス メンバーとして保存するクラスも、そのクリーンアップを担当します。 これにより、参照される破棄可能な型が、Dispose メソッドを介してクリーンアップを確実に実行できるようになります。 この例では、クラスは sealed (または Visual Basic では NotInheritable) です。

using System;

public sealed class Foo : IDisposable
{
    private readonly IDisposable _bar;

    public Foo()
    {
        _bar = new Bar();
    }

    public void Dispose() => _bar.Dispose();
}
Public NotInheritable Class Foo
    Implements IDisposable

    Private ReadOnly _bar As IDisposable

    Public Sub New()
        _bar = New Bar()
    End Sub

    Public Sub Dispose() Implements IDisposable.Dispose
        _bar.Dispose()
    End Sub
End Class

ヒント

  • クラスに IDisposable フィールドまたはプロパティがあるものの "所有" していない場合は、クラスがオブジェクトを作成しないことを意味し、クラスは IDisposable を実装する必要はありません。
  • ファイナライザー (ファイナライザーによって呼び出された null メソッドを含む) で Dispose(false)-checking を実行する必要がある場合があります。 主な理由の 1 つは、インスタンスが完全に初期化されたかどうかがわからない場合です (たとえば、コンストラクターで例外がスローされた可能性がある場合)。

破棄パターンの実装

非シールド クラス (つまり NotInheritable として修飾されない Visual Basic クラス) は、継承される可能性があるため、潜在的な基底クラスと見なす必要があります。 潜在的な基底クラスの破棄パターンを実装する場合、以下を用意する必要があります。

  • Dispose メソッドを呼び出す Dispose(bool) の実装。
  • 実際のクリーンアップを実行する Dispose(bool) メソッド。
  • アンマネージ リソースをラップする SafeHandle から派生したクラス (推奨)、または、Object.Finalize メソッドのオーバーライド。 SafeHandle クラスではファイナライザーが提供されるので、自分で記述する必要はありません。

重要

基底クラスはマネージド オブジェクトを参照するだけで、破棄パターンを実装することができます。 このような場合、ファイナライザーは不要です。 ファイナライザーは、アンマネージ リソースを直接参照する場合にのみ必要です。

セーフ ハンドルを使用して基底クラスで破棄パターンを実装する一般的な例を次に示します。

using Microsoft.Win32.SafeHandles;
using System;
using System.Runtime.InteropServices;

public class BaseClassWithSafeHandle : IDisposable
{
    // To detect redundant calls
    private bool _disposedValue;

    // Instantiate a SafeHandle instance.
    private SafeHandle? _safeHandle = new SafeFileHandle(IntPtr.Zero, true);

    // Public implementation of Dispose pattern callable by consumers.
    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    // Protected implementation of Dispose pattern.
    protected virtual void Dispose(bool disposing)
    {
        if (!_disposedValue)
        {
            if (disposing)
            {
                _safeHandle?.Dispose();
                _safeHandle = null;
            }

            _disposedValue = true;
        }
    }
}
Imports Microsoft.Win32.SafeHandles
Imports System.Runtime.InteropServices

Public Class BaseClassWithSafeHandle
    Implements IDisposable

    ' To detect redundant calls
    Private _disposedValue As Boolean

    ' Instantiate a SafeHandle instance.
    Private _safeHandle As SafeHandle = New SafeFileHandle(IntPtr.Zero, True)

    ' Public implementation of Dispose pattern callable by consumers.
    Public Sub Dispose() _
               Implements IDisposable.Dispose
        Dispose(True)
        GC.SuppressFinalize(Me)
    End Sub

    ' Protected implementation of Dispose pattern.
    Protected Overridable Sub Dispose(ByVal disposing As Boolean)
        If Not _disposedValue Then

            If disposing Then
                _safeHandle?.Dispose()
                _safeHandle = Nothing
            End If

            _disposedValue = True
        End If
    End Sub
End Class

注意

前の例では、SafeFileHandle オブジェクトを使用してパターンを示しています。代わりに、SafeHandle から派生した任意のオブジェクトを使用することもできます。 例では、SafeFileHandle オブジェクトを正しくインスタンス化していないことに注意してください。

Object.Finalize をオーバーライドして基底クラスで Dispose パターンを実装する一般的なパターンを次に示します。

using System;

public class BaseClassWithFinalizer : IDisposable
{
    // To detect redundant calls
    private bool _disposedValue;

    ~BaseClassWithFinalizer() => Dispose(false);

    // Public implementation of Dispose pattern callable by consumers.
    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    // Protected implementation of Dispose pattern.
    protected virtual void Dispose(bool disposing)
    {
        if (!_disposedValue)
        {
            if (disposing)
            {
                // TODO: dispose managed state (managed objects)
            }

            // TODO: free unmanaged resources (unmanaged objects) and override finalizer
            // TODO: set large fields to null
            _disposedValue = true;
        }
    }
}
Public Class BaseClassWithFinalizer
    Implements IDisposable

    ' To detect redundant calls
    Private _disposedValue As Boolean

    Protected Overrides Sub Finalize()
        Dispose(False)
    End Sub

    ' Public implementation of Dispose pattern callable by consumers.
    Public Sub Dispose() _
               Implements IDisposable.Dispose
        Dispose(True)
        GC.SuppressFinalize(Me)
    End Sub

    ' Protected implementation of Dispose pattern.
    Protected Overridable Sub Dispose(ByVal disposing As Boolean)
        If Not _disposedValue Then

            If disposing Then
                ' TODO: dispose managed state (managed objects)
            End If

            ' TODO free unmanaged resources (unmanaged objects) And override finalizer
            ' TODO: set large fields to null
            _disposedValue = True
        End If
    End Sub
End Class

ヒント

C# で終了処理を実装するには、Object.Finalize をオーバーライドするのでなく、ファイナライザーを指定します。 Visual Basic では、Protected Overrides Sub Finalize() を使用してファイナライザーを作成します。

派生クラスの破棄パターンの実装

IDisposable インターフェイスを実装するクラスから派生したクラスは、IDisposable の基底クラスでの実装が派生クラスに継承されるため、IDisposable.Dispose を実装しないでください。 代わりに、派生クラスをクリーンアップするには、以下を用意します。

  • 基底クラスのメソッドをオーバーライドして、派生クラスの実際のクリーンアップを実行する protected override void Dispose(bool) メソッド。 このメソッドは、base.Dispose(bool) (Visual Basic では MyBase.Dispose(bool)) メソッドも呼び出して、破棄状態 (bool disposing パラメーター) を引数として渡す必要があります。
  • アンマネージ リソースをラップする SafeHandle から派生したクラス (推奨)、または、Object.Finalize メソッドのオーバーライド。 SafeHandle クラスには、コーディングが不要なファイナライザーが用意されています。 ファイナライザーを用意する場合は、false 引数を指定して Dispose(bool) オーバーロードを呼び出す必要があります。

セーフ ハンドルを使用して派生クラスで Dispose パターンを実装する一般的なパターンの例を次に示します。

using Microsoft.Win32.SafeHandles;
using System;
using System.Runtime.InteropServices;

public class DerivedClassWithSafeHandle : BaseClassWithSafeHandle
{
    // To detect redundant calls
    private bool _disposedValue;

    // Instantiate a SafeHandle instance.
    private SafeHandle? _safeHandle = new SafeFileHandle(IntPtr.Zero, true);

    // Protected implementation of Dispose pattern.
    protected override void Dispose(bool disposing)
    {
        if (!_disposedValue)
        {
            if (disposing)
            {
                _safeHandle?.Dispose();
                _safeHandle = null;
            }

            _disposedValue = true;
        }

        // Call base class implementation.
        base.Dispose(disposing);
    }
}
Imports Microsoft.Win32.SafeHandles
Imports System.Runtime.InteropServices

Public Class DerivedClassWithSafeHandle
    Inherits BaseClassWithSafeHandle

    ' To detect redundant calls
    Private _disposedValue As Boolean

    ' Instantiate a SafeHandle instance.
    Private _safeHandle As SafeHandle = New SafeFileHandle(IntPtr.Zero, True)

    Protected Overrides Sub Dispose(ByVal disposing As Boolean)
        If Not _disposedValue Then

            If disposing Then
                _safeHandle?.Dispose()
                _safeHandle = Nothing
            End If

            _disposedValue = True
        End If

        ' Call base class implementation.
        MyBase.Dispose(disposing)
    End Sub
End Class

注意

前の例では、SafeFileHandle オブジェクトを使用してパターンを示しています。代わりに、SafeHandle から派生した任意のオブジェクトを使用することもできます。 例では、SafeFileHandle オブジェクトを正しくインスタンス化していないことに注意してください。

Object.Finalize をオーバーライドして派生クラスで Dispose パターンを実装する一般的なパターンを次に示します。

public class DerivedClassWithFinalizer : BaseClassWithFinalizer
{
    // To detect redundant calls
    private bool _disposedValue;

    ~DerivedClassWithFinalizer() => Dispose(false);

    // Protected implementation of Dispose pattern.
    protected override void Dispose(bool disposing)
    {
        if (!_disposedValue)
        {
            if (disposing)
            {
                // TODO: dispose managed state (managed objects).
            }

            // TODO: free unmanaged resources (unmanaged objects) and override a finalizer below.
            // TODO: set large fields to null.
            _disposedValue = true;
        }

        // Call the base class implementation.
        base.Dispose(disposing);
    }
}
Public Class DerivedClassWithFinalizer
    Inherits BaseClassWithFinalizer

    ' To detect redundant calls
    Private _disposedValue As Boolean

    Protected Overrides Sub Finalize()
        Dispose(False)
    End Sub

    ' Protected implementation of Dispose pattern.
    Protected Overrides Sub Dispose(ByVal disposing As Boolean)
        If Not _disposedValue Then

            If disposing Then
                ' TODO: dispose managed state (managed objects).
            End If

            ' TODO free unmanaged resources (unmanaged objects) And override a finalizer below.
            ' TODO: set large fields to null.
            _disposedValue = True
        End If

        ' Call the base class implementation.
        MyBase.Dispose(disposing)
    End Sub
End Class

関連項目