IDisposableインターフェイスを実装する場合、仮にIDisposable.Disposeメソッドの呼び出しが行われなかった場合でもリソースが解放されるように、ファイナライザを実装して解放処理が呼び出されるようにすることができます。 (関連:§.ファイナライザ、§.ファイナライザとIDisposableのデザインパターン (disposeパターン))
一方で、IDisposable.Disposeメソッドで明示的にリソースが破棄された場合、Disposeメソッドが呼び出されなかった場合の保証としてのファイナライザ呼び出しは不要となります。 そのような場合は、GC.SuppressFinalizeメソッドを呼び出すことで、オブジェクトのファイナライザ呼び出しを抑止することができます。
GC.SuppressFinalizeは、ガベージコレクタ(GC)に対してオブジェクトのファイナライザ呼び出しが不要であることを通知します。 ガベージコレクタは、これに基づいてガベージコレクションの際のファイナライザ呼び出しを行わなくなります。
GC.SuppressFinalizeで不要なファイナライザ呼び出しを抑止する
using System;
using System.Runtime.InteropServices;
class UnmanagedMemory : IDisposable {
private IntPtr ptr; // アンマネージメモリ領域を参照するポインタ
public UnmanagedMemory(int size)
{
// アンマネージリソースを確保する
ptr = Marshal.AllocHGlobal(size);
}
// IDisposable.Disposeの実装
public void Dispose()
{
// アンマネージリソースの解放処理を行う
Free();
// 以降このオブジェクトに対するファイナライザ呼び出しは不要であることをガベージコレクタに通知する
GC.SuppressFinalize(this);
}
// デストラクタ(ファイナライザ)の実装
// (Disposeメソッドが呼び出されている場合は、GC.SuppressFinalizeでファイナライザの
// 呼び出しが抑止されるため、呼び出されない)
~UnmanagedMemory()
{
// アンマネージリソースの解放処理を行う
Free();
}
private void Free()
{
// すでに解放されている場合は何もしない(二重解放の抑止)
if (ptr == IntPtr.Zero)
return;
// 解放されていなければ、解放処理を行う
Marshal.FreeHGlobal(ptr);
// 解放済みであることを示すためにIntPtr.Zeroを設定する
ptr = IntPtr.Zero;
}
}
class Sample {
static void Main()
{
// IDisposableインターフェイスを実装するオブジェクトを作成する
// (本来ならusingステートメントを使ったほうがよいが、例示のため省略)
var mem = new UnmanagedMemory(4);
// Disposeメソッドが明示的に呼び出されなかった場合を想定
// mem.Dispose();
// 以降、ガベージコレクタによってオブジェクトが回収される場合、
// ファイナライザが呼び出され、アンマネージリソースが解放される
}
}
.NET Core/.NET 5以降では、.NETランタイムのシャットダウン時(≒.NETプロセスの終了時)におけるファイナライザ呼び出しは行われません。 このため、上記の実装では、必ずしもファイナライザによってアンマネージリソースが解放されるとは限りません。 詳しくは§.シャットダウン時におけるファイナライザ呼び出しを参照してください。