Dispose メソッドの実装

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

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

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

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

ヒント

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

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

セーフ ハンドル

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

System.Runtime.InteropServices.SafeHandle は、アンマネージ リソースを識別する System.IntPtr をラップする抽象マネージド型です。 Windows ではハンドルを識別しますが、Unix ではファイル記述子を識別します。 これが、このリソースが 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 名前空間の次の派生クラスは、セーフ ハンドルを提供します。

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

破棄パターンの実装

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

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

重要

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

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

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

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);

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

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

Class BaseClassWithSafeHandle : Implements IDisposable
    ' Flag: Has Dispose already been called?
    Dim disposed As Boolean = False
    ' Instantiate a SafeHandle instance.
    Dim handle 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(disposing As Boolean)
        If disposed Then Return

        If disposing Then
            handle.Dispose()
        End If

        disposed = True
    End Sub
End Class

注意

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

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

using System;

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;
        }
    }
}
Class BaseClassWithFinalizer : Implements IDisposable
    ' Flag: Has Dispose already been called?
    Dim disposed As Boolean = False

    ' 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(disposing As Boolean)
        If disposed Then Return

        If disposing Then
            ' Dispose managed objects that implement IDisposable.
            ' Assign null to managed objects that consume large amounts of memory or consume scarce resources.
        End If

        ' Free any unmanaged objects here.
        '
        disposed = True
    End Sub

    Protected Overrides Sub Finalize()
        Dispose(False)
    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;

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();
            }

            _disposedValue = true;
        }

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

Class DerivedClassWithSafeHandle : Inherits BaseClassWithSafeHandle
    ' Flag: Has Dispose already been called?
    Dim disposed As Boolean = False
    ' Instantiate a SafeHandle instance.
    Dim handle As SafeHandle = New SafeFileHandle(IntPtr.Zero, True)

    ' Protected implementation of Dispose pattern.
    Protected Overrides Sub Dispose(disposing As Boolean)
        If disposed Then Return

        If disposing Then
            handle.Dispose()
            ' Free any other managed objects here.
            '
        End If

        ' Free any unmanaged objects here.
        '
        disposed = True

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

注意

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

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

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

    ~DerivedClassWithFinalizer() => this.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);
    }
}
Class DerivedClassWithFinalizer : Inherits BaseClassWithFinalizer
    ' Flag: Has Dispose already been called?
    Dim disposed As Boolean = False

    ' Protected implementation of Dispose pattern.
    Protected Overrides Sub Dispose(disposing As Boolean)
        If disposed Then Return

        If disposing Then
            ' Dispose managed objects that implement IDisposable.
            ' Assign null to managed objects that consume large amounts of memory or consume scarce resources.
        End If

        ' Free any unmanaged objects here.
        '
        disposed = True

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

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

関連項目