Dispose メソッドを実装する
Dispose メソッドは、主にアンマネージ リソースを解放するために実装されます。 実装 IDisposable インスタンス メンバーを操作する場合、Dispose 呼び出しを連鎖させるのが一般的です。 Disposeを実装する理由は他にもあります。たとえば、割り当てられたメモリの解放、コレクションに追加された項目の削除、取得されたロックの解放の通知などです。
.NET ガベージ コレクター は、アンマネージ メモリの割り当てや解放を行いません。 破棄パターンと呼ばれるオブジェクトを破棄するパターンでは、オブジェクトの有効期間に順序が適用されます。 dispose パターンは、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 ではファイル記述子を識別できます。 SafeHandle
は、SafeHandle
が破棄されたとき、または SafeHandle
へのすべての参照が削除され、SafeHandle
インスタンスが終了したときに、このリソースが 1 回だけ解放されるようにするために必要なすべてのロジックを提供します。
System.Runtime.InteropServices.SafeHandle は抽象基本クラスです。 派生クラスは、さまざまな種類のハンドルに対して特定のインスタンスを提供します。 これらの派生クラスは、無効と見なされる System.IntPtr の値と、実際にハンドルを解放する方法を検証します。 たとえば、SafeFileHandle は、開いているファイル ハンドル/記述子を識別する IntPtrs
をラップする SafeHandle
から派生し、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) の実装が必要です。 また、シールされていないクラスには、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
パラメーターは、メソッドの呼び出しが Dispose メソッド (その値が true
) またはファイナライザー (その値が false
) から取得されるかどうかを示す Boolean です。
protected virtual void Dispose(bool disposing)
{
if (_disposed)
{
return;
}
if (disposing)
{
// Dispose managed state (managed objects).
// ...
}
// Free unmanaged resources.
// ...
_disposed = true;
}
Protected Overridable Sub Dispose(disposing As Boolean)
If disposed Then Exit Sub
If disposing Then
' Free managed resources.
' ...
End If
' Free unmanaged resources.
' ...
disposed = True
End Sub
重要
disposing
パラメーターは、ファイナライザーから呼び出されたときに false
し、IDisposable.Dispose メソッドから呼び出されたときに true
する必要があります。 つまり、決定論的に呼び出されたときに true
され、非決定的に呼び出されたときに false
されます。
メソッドの本体は、次の 3 つのコード ブロックで構成されます。
オブジェクトが既に破棄されている場合のための条件付きの戻り値ブロック。
マネージド リソースを解放する条件付きブロック。 このブロックは、
disposing
の値がtrue
場合に実行されます。 解放されるマネージド リソースには、次のものが含まれます。- IDisposableを実装するマネージド オブジェクト。 条件付きブロックを使用して Dispose の実装を呼び出すことができます (カスケード破棄)。 System.Runtime.InteropServices.SafeHandle の派生クラスを使用してアンマネージ リソースをラップした場合は、ここで SafeHandle.Dispose() 実装を呼び出す必要があります。
- 大量のメモリを消費したり、不足しているリソースを消費したりするマネージド オブジェクト。
null
に大きなマネージド オブジェクト参照を割り当てて、到達不能になる可能性を高めます。 これにより、非決定的に回収された場合よりも速く解放されます。
管理されていないリソースを解放するブロック。 このブロックは、
disposing
パラメーターの値に関係なく実行されます。
メソッド呼び出しがファイナライザーから来ている場合は、アンマネージ リソースを解放するコードのみを実行する必要があります。 実装者には、誤ったパスが破棄された可能性のあるマネージドオブジェクトと干渉しないよう確認する責任があります。 これは重要です。終了時にガベージ コレクターがマネージド オブジェクトを破棄する順序は非決定的であるためです。
カスケード破棄呼び出し
クラスがフィールドまたはプロパティを所有し、その型が 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を実装する必要はありません。
- ファイナライザー (ファイナライザーによって呼び出された
Dispose(false)
メソッドを含む) でnull
チェックを実行する必要がある場合があります。 主な理由の一つは、インスタンスが完全に初期化されたかどうか確信が持てない場合です(たとえば、コンストラクターで例外が発生する可能性があります)。
"このディスポーズパターンを実装する"
非シール クラス (または NotInheritable
として変更されていない Visual Basic クラス) はすべて、継承される可能性があるため、基底クラスと見なす必要があります。 基底クラスの可能性がある場合に dispose パターンを実装する場合は、次を指定する必要があります。
Dispose(bool)
メソッドを呼び出す Dispose 実装。- 実際のクリーンアップを実行する
Dispose(bool)
メソッド。 - アンマネージ リソースをラップする SafeHandle から派生したクラス (推奨)、または Object.Finalize メソッドのオーバーライドのいずれか。 SafeHandle クラスはファイナライザーを提供するため、自分でファイナライザーを記述する必要はありません。
重要
基底クラスでは、マネージド オブジェクトのみを参照し、dispose パターンを実装できます。 このような場合、ファイナライザーは不要です。 ファイナライザーは、アンマネージ リソースを直接参照する場合にのみ必要です。
セーフ ハンドルを使用する基底クラスの dispose パターンを実装する一般的な例を次に示します。
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()
を使用してファイナライザーを作成します。
派生クラスの dispose パターンを実装する
IDisposable.Dispose の基底クラスの実装は派生クラスによって継承されるため、IDisposable インターフェイスを実装するクラスから派生したクラスは、IDisposableを実装しないでください。 代わりに、派生クラスをクリーンアップするには、次の情報を指定します。
- 基底クラス メソッドをオーバーライドし、派生クラスの実際のクリーンアップを実行する
protected override void Dispose(bool)
メソッド。 このメソッドは、引数として破棄状態 (bool disposing
パラメーター) を渡すbase.Dispose(bool)
(Visual Basic のMyBase.Dispose(bool)
) メソッドも呼び出す必要があります。 - アンマネージ リソースをラップする 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