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 のどの値が無効と見なされるかを検証し、実際にハンドルを解放する方法を検証します。 たとえば、SafeFileHandle は SafeHandle
から派生し、開いているファイル ハンドル/記述子を識別する 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
パラメーターは、ファイナライザーから呼び出されたときは false
、IDisposable.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
関連項目
.NET