.NET Frameworkでは、不要と判断されたオブジェクトはガベージコレクタによって自動的に収集されます。 deleteといった構文による明示的なオブジェクトの解放はできません。

オブジェクトがガベージコレクタによって解放される際にはファイナライザ(デストラクタ)が呼び出されますが、マネージリソースはガベージコレクタによって解放されるため、通常ファイナライザをオーバーライドして解放処理を記述する必要はありません。 逆に、ガベージコレクタによって解放されないアンマネージリソースなどを扱う場合や、明示的な解放処理を実装する必要がある場合には、Disposeメソッド(IDisposableインターフェイス)を実装したり、必要に応じてファイナライザをオーバーライドします。

ファイナライザ

アンマネージリソースの解放処理は、ファイナライザ(Object.Finalizeメソッド)をオーバーライドすることで実装できます。

ただし、ガベージコレクタによるメモリ収集の負荷を増やす原因となるため、解放すべきアンマネージリソースが無いのに空のファイナライザを記述するなど、不必要にオーバーライドすることは避けるべきです。 また、リソースの解放を目的としてファイナライザを実装する場合は、後述するIDisposableインターフェイスと合わせて実装します。

以下、適切な解放処理を実装するためにまずはファイナライザの実装方法と、ファイナライザの動作について見ていきます。

ファイナライザの実装

C#ではデストラクタ構文、VBではFinalizeメソッドのオーバーライドによってファイナライザを実装することができます。 ただし、構造体にはファイナライザを記述することはできません。 ファイナライザを記述できるのはクラスのみです。

以下はコンストラクタにおいて確保したアンマネージリソースをファイナライザで解放するように実装した例です。 ここでは、アンマネージリソースの一例としてMarshal.AllocHGlobalでアンマネージメモリ領域を確保・解放しています。

ファイナライザを実装する
using System;
using System.Runtime.InteropServices;

class UnmanagedMemory {
  public IntPtr Ptr;

  public UnmanagedMemory(int length)
  {
    // アンマネージリソースを確保
    Ptr = Marshal.AllocHGlobal(length);
  }

  // デストラクタ
  ~UnmanagedMemory()
  {
    // インスタンスが保持しているアンマネージリソースを解放する
    Marshal.FreeHGlobal(Ptr);
  }
}

class Sample {
  static void Main()
  {
    UnmanagedMemory m = new UnmanagedMemory(4);

    Marshal.WriteInt32(m.Ptr, 16);

    Console.WriteLine(Marshal.ReadInt32(m.Ptr));
  }
}
ファイナライザを実装する
Imports System
Imports System.Runtime.InteropServices

Class UnmanagedMemory
  Public Ptr As IntPtr

  Public Sub New(ByVal size As Integer)
    ' アンマネージリソースを確保
    Ptr = Marshal.AllocHGlobal(size)
  End Sub

  ' ファイナライザ
  Protected Overrides Sub Finalize()
    ' インスタンスが保持しているアンマネージリソースを解放する
    Marshal.FreeHGlobal(Ptr)
  End Sub
End Class

Class Sample
  Shared Sub Main()
    Dim m As New UnmanagedMemory(4)

    Marshal.WriteInt32(m.Ptr, 16)

    Console.WriteLine(Marshal.ReadInt32(m.Ptr))
  End Sub
End Class

本来、外部から解放されたり変更されないようにUnmanagedMemory.Ptrは非公開あるいは読み取り専用にすべきですが、ここでは例示のためパブリックフィールドとしています

ガベージコレクタとファイナライザの動作

ファイナライザはガベージコレクタによって呼び出され、またdeleteなどの構文によって明示的に呼び出すことはできません。 そのため、ファイナライザが呼び出されるタイミングを制御することは出来ません。 次のコードでファイナライザが呼び出されるタイミングを調べてみます。

ファイナライザが呼び出されるタイミング
using System;
using System.Diagnostics;

class Sample {
  static Stopwatch sw = Stopwatch.StartNew();

  static void Print(string message)
  {
    Console.WriteLine(string.Format("{0:D12}: {1}", sw.ElapsedTicks, message));
  }

  class TestObject {
    public TestObject()
    {
      Print("コンストラクタ");
    }

    ~TestObject()
    {
      Print("ファイナライザ");
    }
  }

  static void Create()
  {
    Print("Create開始");

    new TestObject();

    Print("Create終了");
  }

  static void Main()
  {
    Print("Main開始");

    Create();

    Print("Main終了");
  }
}
ファイナライザが呼び出されるタイミング
Imports System
Imports System.Diagnostics

Class Sample
  Shared Dim sw As Stopwatch = Stopwatch.StartNew()

  Shared Sub Print(ByVal message As String)
    Console.WriteLine(string.Format("{0:D12}: {1}", sw.ElapsedTicks, message))
  End Sub

  Class TestObject
    Public Sub New()
      Print("コンストラクタ")
    End Sub

    Protected Overrides Sub Finalize()
      Print("ファイナライザ")
    End Sub
  End Class

  Shared Sub Create()
    Print("Create開始")

    Dim o As New TestObject()

    Print("Create終了")
  End Sub

  Shared Sub Main()
    Print("Main開始")

    Create()

    Print("Main終了")
  End Sub
End Class
実行結果
# 1回目
000000002407: Main開始
000000060602: Create開始
000000061268: コンストラクタ
000000061741: Create終了
000000062056: Main終了
000000067165: ファイナライザ

# 2回目
000000002552: Main開始
000000063533: Create開始
000000064280: コンストラクタ
000000064526: Create終了
000000064728: Main終了
000000070562: ファイナライザ

# 3回目
000000002422: Main開始
000000061475: Create開始
000000062145: コンストラクタ
000000062666: Create終了
000000062962: Main終了
000000068063: ファイナライザ

実行結果を見てわかるとおり、ファイナライザが呼び出されるタイミングは必ずしもメソッドから出た直後にはなりません。 C++などの言語では、メソッド内(あるいはブロック内)で使用された変数はメソッドが終了する時点(スコープから出た時点)で解放されますが、上記の結果のように生成されたインスタンスはメソッドが終了した後も多少の間存在し続けていることがわかります。

GC.Collectメソッドを用いることにより強制的にガベージコレクションを実行させることができ、これによってファイナライザを呼び出させることはできます。 このメソッドは必要性のない限り呼び出すべきものではありませんが、実験のため先の例を書き換えてGC.Collectメソッドを呼び出すようにしてみます。

GC.Collectメソッドによって強制的にガベージコレクションを実行させた場合
using System;
using System.Diagnostics;

class Sample {
  static Stopwatch sw = Stopwatch.StartNew();

  static void Print(string message)
  {
    Console.WriteLine(string.Format("{0:D12}: {1}", sw.ElapsedTicks, message));
  }

  class TestObject {
    public TestObject()
    {
      Print("コンストラクタ");
    }

    ~TestObject()
    {
      Print("ファイナライザ");
    }
  }

  static void Create()
  {
    Print("Create開始");

    new TestObject();

    Print("Create終了");
  }

  static void Main()
  {
    Print("Main開始");

    Create();

    Print("GC.Collect");

    GC.Collect();
    GC.WaitForPendingFinalizers();

    Print("Main終了");
  }
}
GC.Collectメソッドによって強制的にガベージコレクションを実行させた場合
Imports System
Imports System.Diagnostics

Class Sample
  Shared Dim sw As Stopwatch = Stopwatch.StartNew()

  Shared Sub Print(ByVal message As String)
    Console.WriteLine(string.Format("{0:D12}: {1}", sw.ElapsedTicks, message))
  End Sub

  Class TestObject
    Public Sub New()
      Print("コンストラクタ")
    End Sub

    Protected Overrides Sub Finalize()
      Print("ファイナライザ")
    End Sub
  End Class

  Shared Sub Create()
    Print("Create開始")

    Dim o As New TestObject()

    Print("Create終了")
  End Sub

  Shared Sub Main()
    Print("Main開始")

    Create()

    Print("GC.Collect")

    GC.Collect()
    GC.WaitForPendingFinalizers()

    Print("Main終了")
  End Sub
End Class
実行結果
# 1回目
000000002658: Main開始
000000065939: Create開始
000000066750: コンストラクタ
000000067031: Create終了
000000067291: GC.Collect
000000073815: ファイナライザ
000000075074: Main終了

# 2回目
000000002667: Main開始
000000064264: Create開始
000000065048: コンストラクタ
000000065327: Create終了
000000065588: GC.Collect
000000072155: ファイナライザ
000000073434: Main終了

# 3回目
000000002405: Main開始
000000060244: Create開始
000000061222: コンストラクタ
000000061528: Create終了
000000061810: GC.Collect
000000066227: Main終了
000000069624: ファイナライザ

このように、(必ずではありませんが)GC.Collectにより参照されなくなったオブジェクトのファイナライザが呼び出されるようになります。 なお、上記の例では、GC.WaitForPendingFinalizersメソッドを呼び出すことで収集対象となっているファイナライザの実行が終了するまで待機しています。

IDisposableインターフェイス

ファイナライザによるリソースの解放の場合、ファイナライザが実行されるタイミングを制御することができません。 そのため、リソースを任意のタイミングで速やかに解放したい場合には、ファイナライザに頼らない方法で解放処理を実装する必要があります。 そのような目的に使用できるのがIDisposableインターフェイスです。 このインターフェイスは、型が解放すべきリソースを保持していることを表すと同時に、それを解放するためのメソッドDisposeを提供します。

以下の例は、先に挙げたファイナライザによる解放のかわりにIDisposableインターフェイスを実装したものです。

IDisposableインターフェイスを実装する
using System;
using System.Runtime.InteropServices;

class UnmanagedMemory : IDisposable {
  public IntPtr Ptr;

  public UnmanagedMemory(int size)
  {
    Ptr = Marshal.AllocHGlobal(size);
  }

  // IDisposable.Disposeの実装
  public void Dispose()
  {
    if (Ptr != IntPtr.Zero) {
      Marshal.FreeHGlobal(Ptr);

      // 解放済みであることを示すためにIntPtr.Zeroを代入
      Ptr = IntPtr.Zero;
    }
  }
}

class Sample {
  static void Main()
  {
    UnmanagedMemory m = new UnmanagedMemory(4);

    Marshal.WriteInt32(m.Ptr, 16);

    Console.WriteLine(Marshal.ReadInt32(m.Ptr));

    // Disposeメソッドでアンマネージリソースを解放する
    m.Dispose();
  }
}
IDisposableインターフェイスを実装する
Imports System
Imports System.Runtime.InteropServices

Class UnmanagedMemory
  Implements IDisposable

  Public Ptr As IntPtr

  Public Sub New(ByVal size As Integer)
    Ptr = Marshal.AllocHGlobal(size)
  End Sub

  ' IDisposable.Disposeの実装
  Public Sub Dispose() Implements IDisposable.Dispose
    If Ptr <> IntPtr.Zero
      Marshal.FreeHGlobal(Ptr)

      ' 解放済みであることを示すためにIntPtr.Zeroを代入
      Ptr = IntPtr.Zero
    End If
  End Sub
End Class

Class Sample
  Shared Sub Main()
    Dim m As New UnmanagedMemory(4)

    Marshal.WriteInt32(m.Ptr, 16)

    Console.WriteLine(Marshal.ReadInt32(m.Ptr))

    ' Disposeメソッドでアンマネージリソースを解放する
    m.Dispose()
  End Sub
End Class

このように、Disposeメソッドを呼び出すことで明示的にインスタンスの破棄(リソースの解放など)を行えるようになります。 もちろん、IDisposeインターフェイスを使わなくても、ReleaseやCloseといったメソッドを用意しておいて、それらのメソッドでリソースの解放を行うようにすることもできます。 しかし、IDisposableを実装することにより、型が解放されるべきリソースを保持していることを明示できるほか、usingステートメント(後述)を使った確実な解放処理の記述が行えるようになります。

なお、IDisposableインターフェイスではIsDisposedのようなオブジェクトが破棄されているかどうかを知るためのプロパティは提供されません。 リソースを使用する側では通常Disposeメソッドを一度呼び出した後はそのオブジェクトを参照しなくなるケースがほとんどなので、適切なリソース使用・破棄の処理を記述していればこういったプロパティはあまり必要になるものではありませんが、もし必要ならインターフェイスの実装とは別に用意することも出来ます。

また、IDisposableインターフェイスではインスタンスがすでに破棄されているかどうかを知ることができないことから、ひとつのオブジェクトに対してDisposeメソッドが複数回呼び出される可能性があります。 そのため、IDisposableインターフェイスを実装する場合は、複数回Disposeメソッドが呼び出された場合でも既に解放したリソースを再度解放しないように実装する必要があります。 特に、Disposeメソッドからは例外ObjectDisposedException(後述)をスローしないようにします。

IDisposableとファイナライザ

IDisposableインターフェイスは、あくまでリソースの解放する手段を提供するためのもので、リソースを確実に解放することを保証するためのものではありません。 IDisposableを実装することで明示的にリソースを解放することは出来ますが、Disposeメソッドが呼び出されない場合はリソースが解放されないままとなってしまいます。 単にDisposeメソッドの呼び出しをし忘れた場合や、Disposeメソッドを呼び出す前に例外が発生してDisposeメソッドの呼び出しが行われない場合などにこのような状況が発生します。

そこでIDisposableを実装する場合は、Disposeメソッドが呼び出されなかった場合でも最終的にガベージコレクタがオブジェクトを解放する時点でリソースが解放されるよう、ファイナライザもオーバーライドします。

Disposeメソッドが呼び出されなかった場合でもファイナライザから解放処理を呼び出すようにする例
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;

class UnmanagedMemory : IDisposable {
  public IntPtr Ptr;

  public UnmanagedMemory(int size)
  {
    Ptr = Marshal.AllocHGlobal(size);

    Trace.WriteLine(string.Format("allocated: Ptr = {0}", Ptr));
  }

  ~UnmanagedMemory()
  {
    Free();

    Trace.WriteLine(string.Format("finalized: Ptr = {0}", Ptr));
  }

  public void Dispose()
  {
    Free();

    Trace.WriteLine(string.Format("disposed: Ptr = {0}", Ptr));
  }

  private void Free()
  {
    if (Ptr != IntPtr.Zero) {
      Marshal.FreeHGlobal(Ptr);

      // 解放済みであることを示すためにIntPtr.Zeroを代入
      Ptr = IntPtr.Zero;
    }
  }
}

class Sample {
  static void Main()
  {
    UnmanagedMemory m = new UnmanagedMemory(4);

    Marshal.WriteInt32(m.Ptr, 16);

    Console.WriteLine(Marshal.ReadInt32(m.Ptr));

    // Disposeによる明示的な解放を行わない
    //m.Dispose();
  }
}
Disposeメソッドが呼び出されなかった場合でもファイナライザから解放処理を呼び出すようにする例
Imports System
Imports System.Diagnostics
Imports System.Runtime.InteropServices

Class UnmanagedMemory
  Implements IDisposable

  Public Ptr As IntPtr

  Public Sub New(ByVal size As Integer)
    Ptr = Marshal.AllocHGlobal(size)

    Trace.WriteLine(string.Format("allocated: Ptr = {0}", Ptr))
  End Sub

  Protected Overrides Sub Finalize()
    Free()

    Trace.WriteLine(string.Format("finalized: Ptr = {0}", Ptr))
  End Sub

  Public Sub Dispose() Implements IDisposable.Dispose
    Free()

    Trace.WriteLine(string.Format("disposed: Ptr = {0}", Ptr))
  End Sub

  Private Sub Free()
    If Ptr <> IntPtr.Zero
      Marshal.FreeHGlobal(Ptr)

      ' 解放済みであることを示すためにIntPtr.Zeroを代入
      Ptr = IntPtr.Zero
    End If
  End Sub
End Class

Class Sample
  Shared Sub Main()
    Dim m As New UnmanagedMemory(4)

    Marshal.WriteInt32(m.Ptr, 16)

    Console.WriteLine(Marshal.ReadInt32(m.Ptr))

    ' Disposeによる明示的な解放を行わない
    'm.Dispose()
  End Sub
End Class
実行結果
allocated: Ptr = 150808256
16
finalized: Ptr = 0

このように実装することでDisposeが呼ばれない場合でも確実に解放するように出来ます。

しかし、Disposeが呼ばれた場合にはリソースは解放済みとなるためファイナライザの呼び出しは不要となる一方で、Disposeされているかどうかに関わらずガベージコレクタによってファイナライザの呼び出しが行われることになります。 そのため、上記の例でコメントアウトしているDisposeメソッドの呼び出しを有効にして実行すると、次のようにDisposeとファイナライザの両方が呼び出される結果になります。

明示的にDisposeメソッドを呼び出すようにした場合の実行結果
allocated: Ptr = 150643120
16
disposed: Ptr = 0
finalized: Ptr = 0

そこで、Disposeメソッドが呼び出された場合にはファイナライザの実行が不要であることをガベージコレクタに伝えるため、DisposeメソッドでGC.SuppressFinalizeメソッドを呼び出します。 先の例におけるDisposeメソッドに、次のようなGC.SuppressFinalizeメソッドの呼び出しを追加します。

Disposeメソッドが呼び出された場合にファイナライザの実行を行わないようにする
  public void Dispose()
  {
    Free();

    // このインスタンスに対するファイナライザの呼び出しが不要であることを伝える
    GC.SuppressFinalize(this);

    Trace.WriteLine(string.Format("disposed: Ptr = {0}", Ptr));
  }
Disposeメソッドが呼び出された場合にファイナライザの実行を行わないようにする
  Public Sub Dispose() Implements IDisposable.Dispose
    Free()

    ' このインスタンスに対するファイナライザの呼び出しが不要であることを伝える
    GC.SuppressFinalize(Me)

    Trace.WriteLine(string.Format("disposed: Ptr = {0}", Ptr))
  End Sub

すると、Disposeメソッドが呼び出された場合はファイナライザの呼び出しはされなくなります。

GC.SuppressFinalizeメソッドによってファイナライザの呼び出しを行わないようにした場合の実行結果
allocated: Ptr = 142677440
16
disposed: Ptr = 0

ファイナライザとDisposeメソッドの適切な実装方法については後述の§.ファイナライザとIDisposableのデザインパターンも参照してください。

解放されたリソースへのアクセス拒否 (ObjectDisposedException)

Disposeメソッドによって任意のタイミングでリソースを解放できるようにした場合、既に破棄されたオブジェクトから解放済みのリソースにアクセスしようとしてしまう場合が発生します。 そのような操作を許可しない場合にスローする例外がObjectDisposedExceptionです。

ObjectDisposedExceptionは、破棄されたオブジェクトに対してメソッド呼び出しやメンバ参照を行った際にスローします。 これにより解放済みのリソースにアクセスすることを防ぎます。 ObjectDisposedExceptionのコンストラクタの引数には例外をスローしたオブジェクトの名前を指定できるので、Type.FullNameなどを指定します。 これにより、ObjectDisposedExceptionが発生した原因となったオブジェクトを通知することができます。

以下の例は、解放済みのリソースにアクセスしようとした場合にObjectDisposedExceptionをスローするようにしたものです。

リソースが解放済みの場合にObjectDisposedExceptionをスローする例
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;

class UnmanagedMemory : IDisposable {
  private IntPtr ptr;

  public IntPtr Ptr {
    get {
      // リソースが解放済みの場合はObjectDisposedExceptionをスローする
      if (ptr == IntPtr.Zero) throw new ObjectDisposedException(GetType().FullName);

      // 解放されていない場合は参照を返す
      return ptr;
    }
  }

  public UnmanagedMemory(int size)
  {
    ptr = Marshal.AllocHGlobal(size);
  }

  ~UnmanagedMemory()
  {
    Free();
  }

  public void Dispose()
  {
    Free();
    GC.SuppressFinalize(this);
  }

  private void Free()
  {
    if (ptr != IntPtr.Zero) {
      Marshal.FreeHGlobal(ptr);

      ptr = IntPtr.Zero;
    }
  }
}

class Sample {
  static void Main()
  {
    UnmanagedMemory m = new UnmanagedMemory(4);

    // リソースを解放する
    m.Dispose();

    // 解放済みのリソースへのアクセスを試みる
    Marshal.WriteInt32(m.Ptr, 16);
  }
}
リソースが解放済みの場合にObjectDisposedExceptionをスローする例
Imports System
Imports System.Diagnostics
Imports System.Runtime.InteropServices

Class UnmanagedMemory
  Implements IDisposable

  Private _ptr As IntPtr

  Public ReadOnly Property Ptr As IntPtr
    Get
      ' リソースが解放済みの場合はObjectDisposedExceptionをスローする
      If _ptr = IntPtr.Zero Then Throw New ObjectDisposedException(Me.GetType().FullName)

      ' 解放されていない場合は参照を返す
      Return _ptr
    End Get
  End Property

  Public Sub New(ByVal size As Integer)
    _ptr = Marshal.AllocHGlobal(size)
  End Sub

  Protected Overrides Sub Finalize()
    Free()
  End Sub

  Public Sub Dispose() Implements IDisposable.Dispose
    Free()
    GC.SuppressFinalize(Me)
  End Sub

  Private Sub Free()
    If _ptr <> IntPtr.Zero
      Marshal.FreeHGlobal(_ptr)

      _ptr = IntPtr.Zero
    End If
  End Sub
End Class

Class Sample
  Shared Sub Main()
    Dim m As New UnmanagedMemory(4)

    ' リソースを解放する
    m.Dispose()

    ' 解放済みのリソースへのアクセスを試みる
    Marshal.WriteInt32(m.Ptr, 16)
  End Sub
End Class
実行結果
ハンドルされていない例外: System.ObjectDisposedException: 破棄されたオブジェクトにアクセスできません。
オブジェクト名 'UnmanagedMemory' です。
   場所 UnmanagedMemory.ThrowIfDisposed()
   場所 UnmanagedMemory.get_Ptr()
   場所 Sample.Main()

なお、IDisposable.DisposeメソッドからはObjectDisposedExceptionをスローしないようにします。 ObjectDisposedExceptionは、リソースを利用しようとしたが既に解放済みだった場合にスローします。 IDisposable.Disposeは、すでに破棄されたオブジェクトから呼び出された場合でも例外をスローすることなく動作するように実装しなければなりません。

ファイナライザとIDisposableのデザインパターン

.NET Frameworkでは、ファイナライザとIDisposableインターフェイスを実装した破棄可能なクラスのデザインパターンが提示されています。 ファイナライザとIDisposableを実装する際のデザインパターンの詳細については以下のページで解説されています。 このパターンでは、IDisposableを実装するクラスを継承する場合についても示されています。

以下の例は、ファイナライザとIDisposableを上記のデザインパターンに従って破棄可能なクラスを実装したものです。 まずは、継承を許可しない型での実装例です。

.NET Frameworkのデザインパターンに従ったファイナライザとIDisposableの実装(継承を許可しない場合)
using System;
using System.IO;
using System.Runtime.InteropServices;

// 使用後に解放されるべきリソースを扱うクラス
sealed class Disposable : IDisposable
{
  private bool disposed = false; // リソースが破棄(解放)されていることを表すフラグ
  private IntPtr ptr = Marshal.AllocHGlobal(4); // コンストラクタ等で確保されるアンマネージリソースを想定したオブジェクト
  private Stream resourceStream = Stream.Null; // コンストラクタ等で確保されるマネージリソースを想定したオブジェクト

  // ファイナライザ
  ~Disposable()
  {
    // アンマネージリソースのみを破棄させる
    Dispose(false);
  }

  // IDisposable.Disposeの実装
  public void Dispose()
  {
    // アンマネージリソースと、マネージリソースの両方を破棄させる
    Dispose(true);
    // すべてのリソースが破棄されているため、以後ファイナライザの実行は不要であることをガベージコレクタに通知する
    GC.SuppressFinalize(this);
  }

  // リソースの解放処理を行うためのメソッド
  private void Dispose(bool disposing)
  {
    // 既にリソースが破棄されている場合は何もしない
    if (disposed) return;

    // アンマネージリソースと、マネージリソースの両方の破棄が要求された場合
    if (disposing) {
      // 破棄されていないマネージリソースの解放処理を行う
      if (resourceStream != null) {
        resourceStream.Close();
        resourceStream = null;
      }
    }

    // 破棄されていないアンマネージリソースの解放処理を行う
    if (ptr != IntPtr.Zero) {
      Marshal.FreeHGlobal(ptr);
      ptr = IntPtr.Zero;
    }

    // リソースは破棄されている
    disposed = true;
  }

  // 既にリソースが破棄されているかチェックして、ObjectDisposedExceptionをスローするためのメソッド
  private void ThrowIfDisposed()
  {
    if (disposed) throw new ObjectDisposedException(GetType().FullName);
  }

  // 保持しているリソースを使って何らかの操作を行うメソッドを想定
  public string SomeOperation()
  {
    // 既にリソースが破棄されているかチェックする
    ThrowIfDisposed();

    // リソースが利用可能な場合はそれを使った操作を行い、結果を返す
    return "operation result";
  }
}
.NET Frameworkのデザインパターンに従ったファイナライザとIDisposableの実装(継承を許可しない場合)
Imports System
Imports System.IO
Imports System.Runtime.InteropServices

' 使用後に解放されるべきリソースを扱うクラス
NotInheritable Class Disposable
  Implements IDisposable

  Private disposed As Boolean = False ' リソースが破棄(解放)されていることを表すフラグ
  Private ptr As IntPtr = Marshal.AllocHGlobal(4) ' コンストラクタ等で確保されるアンマネージリソースを想定したオブジェクト
  Private resourceStream As Stream = Stream.Null ' コンストラクタ等で確保されるマネージリソースを想定したオブジェクト

  ' ファイナライザ
  Protected Overrides Sub Finalize()
    ' アンマネージリソースのみを破棄させる
    Dispose(false)
  End Sub

  ' IDisposable.Disposeの実装
  Public Sub Dispose() Implements IDisposable.Dispose
    ' アンマネージリソースと、マネージリソースの両方を破棄させる
    Dispose(true)
    ' すべてのリソースが破棄されているため、以後ファイナライザの実行は不要であることをガベージコレクタに通知する
    GC.SuppressFinalize(Me)
  End Sub

  ' リソースの解放処理を行うためのメソッド
  Protected Overridable Sub Dispose(ByVal disposing As Boolean)
    ' 既にリソースが破棄されている場合は何もしない
    If disposed Then Return

    ' アンマネージリソースと、マネージリソースの両方の破棄が要求された場合
    If disposing Then
      ' 破棄されていないマネージリソースの解放処理を行う
      If Not resourceStream Is Nothing Then
        resourceStream.Close()
        resourceStream = Nothing
      End If
    End If

    ' 破棄されていないアンマネージリソースの解放処理を行う
    If ptr <> IntPtr.Zero Then
      Marshal.FreeHGlobal(ptr)
      ptr = IntPtr.Zero
    End If

    ' リソースは破棄されている
    disposed = True
  End Sub

  ' 既にリソースが破棄されているかチェックして、ObjectDisposedExceptionをスローするためのメソッド
  Private Sub ThrowIfDisposed()
    If disposed Then Throw New ObjectDisposedException(Me.GetType().FullName)
  End Sub

  ' 保持しているリソースを使って何らかの操作を行うメソッドを想定
  Public Function SomeOperation() As String
    ' 既にリソースが破棄されているかチェックする
    ThrowIfDisposed()

    ' リソースが利用可能な場合はそれを使った操作を行い、結果を返す
    Return "operation result"
  End Function
End Class

次に、継承を許可する破棄可能なクラス、およびデザインパターンに従った破棄可能なクラスを継承する場合の実装例です。

.NET Frameworkのデザインパターンに従ったファイナライザとIDisposableの実装(継承を行う場合)
using System;
using System.IO;
using System.Runtime.InteropServices;

// 使用後に解放されるべきリソースを扱うクラス
class DisposableBase : IDisposable {
  private bool disposed = false; // リソースが破棄(解放)されていることを表すフラグ
  private IntPtr ptr = Marshal.AllocHGlobal(4); // コンストラクタ等で確保されるアンマネージリソースを想定したオブジェクト

  // ファイナライザ
  ~DisposableBase()
  {
    // アンマネージリソースのみを破棄させる
    Dispose(false);
  }

  // IDisposable.Disposeの実装
  public void Dispose()
  {
    // アンマネージリソースと、マネージリソースの両方を破棄させる
    Dispose(true);
    // すべてのリソースが破棄されているため、以後ファイナライザの実行は不要であることをガベージコレクタに通知する
    GC.SuppressFinalize(this);
  }

  // リソースの解放処理を行うためのメソッド
  protected virtual void Dispose(bool disposing)
  {
    // 既にリソースが破棄されている場合は何もしない
    if (disposed) return;

    // 破棄されていないアンマネージリソースの解放処理を行う
    if (ptr != IntPtr.Zero) {
      Marshal.FreeHGlobal(ptr);
      ptr = IntPtr.Zero;
    }

    // リソースは破棄されている
    disposed = true;
  }

  // 既にリソースが破棄されているかチェックして、ObjectDisposedExceptionをスローするためのメソッド
  protected void ThrowIfDisposed()
  {
    if (disposed) throw new ObjectDisposedException(GetType().FullName);
  }

  // 保持しているリソースを使って何らかの操作を行うメソッドを想定
  public virtual string SomeOperation()
  {
    // 既にリソースが破棄されているかチェックする
    ThrowIfDisposed();

    // リソースが利用可能な場合はそれを使った操作を行い、結果を返す
    return "operation result";
  }
}

// リソース解放処理を持つ型を継承したクラス
class DisposableExtended : DisposableBase {
  private IntPtr extraDataPtr = Marshal.AllocHGlobal(8); // コンストラクタ等で確保されるアンマネージリソースを想定したオブジェクト
  private Stream resourceStream = Stream.Null; // コンストラクタ等で確保されるマネージリソースを想定したオブジェクト

  // 基底クラスのリソース解放処理をオーバーライド
  protected override void Dispose(bool disposing)
  {
    try {
      // アンマネージリソースと、マネージリソースの両方の破棄が要求された場合
      if (disposing) {
        // 破棄されていないマネージリソースの解放処理を行う
        if (resourceStream != null) {
          resourceStream.Close();
          resourceStream = null;
        }
      }

      // 破棄されていないアンマネージリソースの解放処理を行う
      if (extraDataPtr != IntPtr.Zero) {
        Marshal.FreeHGlobal(extraDataPtr);
        extraDataPtr = IntPtr.Zero;
      }
    }
    finally {
      // 例外が発生しても基底クラスのDisposeメソッドを確実に呼び出すようにする
      base.Dispose(disposing);
    }
  }

  public override string SomeOperation()
  {
    // 既にリソースが破棄されているかチェックする
    ThrowIfDisposed();

    // リソースが利用可能な場合はそれを使った操作を行い、結果を返す
    return "operation result";
  }
}
.NET Frameworkのデザインパターンに従ったファイナライザとIDisposableの実装(継承を行う場合)
Imports System
Imports System.IO
Imports System.Runtime.InteropServices

' 使用後に解放されるべきリソースを扱うクラス
Class DisposableBase
  Implements IDisposable

  Private disposed As Boolean = False ' リソースが破棄(解放)されていることを表すフラグ
  Private ptr As IntPtr = Marshal.AllocHGlobal(4) ' コンストラクタ等で確保されるアンマネージリソースを想定したオブジェクト

  ' ファイナライザ
  Protected Overrides Sub Finalize()
    ' アンマネージリソースのみを破棄させる
    Dispose(false)
  End Sub

  ' IDisposable.Disposeの実装
  Public Sub Dispose() Implements IDisposable.Dispose
    ' アンマネージリソースと、マネージリソースの両方を破棄させる
    Dispose(true)
    ' すべてのリソースが破棄されているため、以後ファイナライザの実行は不要であることをガベージコレクタに通知する
    GC.SuppressFinalize(Me)
  End Sub

  ' リソースの解放処理を行うためのメソッド
  Protected Overridable Sub Dispose(ByVal disposing As Boolean)
    ' 既にリソースが破棄されている場合は何もしない
    If disposed Then Return

    ' 破棄されていないアンマネージリソースの解放処理を行う
    If ptr <> IntPtr.Zero Then
      Marshal.FreeHGlobal(ptr)
      ptr = IntPtr.Zero
    End If

    ' リソースは破棄されている
    disposed = True
  End Sub

  ' 既にリソースが破棄されているかチェックして、ObjectDisposedExceptionをスローするためのメソッド
  Protected Sub ThrowIfDisposed()
    If disposed Then Throw New ObjectDisposedException(Me.GetType().FullName)
  End Sub

  ' 保持しているリソースを使って何らかの操作を行うメソッドを想定
  Public Overridable Function SomeOperation() As String
    ' 既にリソースが破棄されているかチェックする
    ThrowIfDisposed()

    ' リソースが利用可能な場合はそれを使った操作を行い、結果を返す
    Return "operation result"
  End Function
End Class

' リソース解放処理を持つ型を継承したクラス
Class DisposableExtended
  Inherits DisposableBase

  Private extraDataPtr As IntPtr = Marshal.AllocHGlobal(8) ' コンストラクタ等で確保されるアンマネージリソースを想定したオブジェクト
  Private resourceStream As Stream = Stream.Null ' コンストラクタ等で確保されるマネージリソースを想定したオブジェクト

  ' 基底クラスのリソース解放処理をオーバーライド
  Protected Overrides Sub Dispose(ByVal disposing As Boolean)
    Try
      ' アンマネージリソースと、マネージリソースの両方の破棄が要求された場合
      If disposing Then
        ' 破棄されていないマネージリソースの解放処理を行う
        If Not resourceStream Is Nothing Then
          resourceStream.Close()
          resourceStream = Nothing
        End If
      End If

      ' 破棄されていないアンマネージリソースの解放処理を行う
      If extraDataPtr <> IntPtr.Zero Then
        Marshal.FreeHGlobal(extraDataPtr)
        extraDataPtr = IntPtr.Zero
      End If
    Finally
      ' 例外が発生しても基底クラスのDisposeメソッドを確実に呼び出すようにする
      MyBase.Dispose(disposing)
    End Try
  End Sub

  Public Overrides Function SomeOperation() As String
    ' 既にリソースが破棄されているかチェックする
    ThrowIfDisposed()

    ' リソースが利用可能な場合はそれを使った操作を行い、結果を返す
    Return "operation result"
  End Function
End Class

usingステートメント

usingステートメントはC#とVBで用意されているもので、try-finally文を使った確実な解放処理をより簡単に記述することが出来ます。 解放処理が必要になる例として、FileStreamを使用する場合を考えます。 try-finally文を使った場合、開いたストリームを確実に閉じるようにするには次のように記述できます。

try-finally文を使った確実な解放処理の記述
using System;
using System.IO;

class Sample {
  static void Main()
  {
    FileStream stream = null;

    try {
      // FileStreamインスタンスを作成して使用する
      stream = File.OpenRead("test.txt");

      StreamReader reader = new StreamReader(stream);

      Console.WriteLine(reader.ReadToEnd());
    }
    finally {
      // try節を抜けた時点でnullでなければFileStreamを閉じる
      // (途中で例外がスローされた場合も含む)
      if (stream != null) stream.Close();
    }
  }
}
try-finally文を使った確実な解放処理の記述
Imports System
Imports System.IO

Class Sample
  Shared Sub Main()
    Dim stream As FileStream = Nothing

    Try
      ' FileStreamインスタンスを作成して使用する
      stream = File.OpenRead("test.txt")

      Dim reader As New StreamReader(stream)

      Console.WriteLine(reader.ReadToEnd())
    Finally
      ' Try節を抜けた時点でnullでなければFileStreamを閉じる
      ' (途中で例外がスローされた場合も含む)
      If Not stream Is Nothing Then stream.Close()
    End Try
  End Sub
End Class

usingステートメントを用いると、上記のコードと同等の処理を以下のように記述することが出来ます。

usingステートメントを使った確実な解放処理の記述
using System;
using System.IO;

class Sample {
  static void Main()
  {
    // FileStreamインスタンスを作成して使用する
    using (FileStream stream = File.OpenRead("test.txt")) {
      StreamReader reader = new StreamReader(stream);

      Console.WriteLine(reader.ReadToEnd());
    }
    // usingステートメントを抜けた時点でstreamは自動的に閉じられる
    // (途中で例外がスローされた場合も含む)
  }
}
usingステートメントを使った確実な解放処理の記述
Imports System
Imports System.IO

Class Sample
  Shared Sub Main()
    ' FileStreamインスタンスを作成して使用する
    Using stream As FileStream = File.OpenRead("test.txt")
      Dim reader As New StreamReader(stream)

      Console.WriteLine(reader.ReadToEnd())
    End Using
    ' Usingステートメントを抜けた時点でstreamは自動的に閉じられる
    ' (途中で例外がスローされた場合も含む)
  End Sub
End Class

usingステートメントはIDisposableインターフェイスを実装している型ならどのような型に対しても使用することができ、そのスコープから抜ける時点でDisposeメソッドが自動的に呼び出されるようになります。 また、usingステートメントはtry-finally文に相当する処理に展開されるため、usingステートメント内で例外が発生した場合でも確実にDisposeメソッドが呼び出されます。 上記の例で使用したFileStreamクラスでは、その基底クラスであるStreamクラスがIDisposableを実装しているため、usingステートメントを使った記述ができるようになっています。

usingステートメントを使うと、確実な解放処理のための記述がtry-finally文よりシンプルになるほか、解放可能なリソースを持つオブジェクトのスコープ(有効範囲)と生存期間が明確になるという利点もあります。

なお、usingステートメントでは複数のオブジェクトを指定することも出来ます。 以下の例はusingステートメントで二つのStreamを使用した例です。

usingステートメントで解放対象のオブジェクトを複数指定する
using System;
using System.IO;

class Sample {
  static void Main()
  {
    using (Stream fromStream = File.OpenRead("source.txt"), toStream = File.OpenWrite("dest.txt")) {
      // ファイルsource.txtの内容をdest.txtにコピーする
      fromStream.CopyTo(toStream);
    }
  }
}
usingステートメントで解放対象のオブジェクトを複数指定する
Imports System
Imports System.IO

Class Sample
  Shared Sub Main()
    Using fromStream As FileStream = File.OpenRead("source.txt"), toStream As FileStream = File.OpenWrite("dest.txt")
      ' ファイルsource.txtの内容をdest.txtにコピーする
      fromStream.CopyTo(toStream)
    End Using
  End Sub
End Class

この例で使用しているCopyToメソッドは.NET Framework 4以降で使用可能なメソッドです。

C#では、これを次のように複数のusingステートメントに分けて記述することもできます。

解放対象のオブジェクトを複数のusingステートメントで指定する
using System;
using System.IO;

class Sample {
  static void Main()
  {
    using (Stream fromStream = File.OpenRead("source.txt"))
    using (Stream toStream = File.OpenWrite("dest.txt")) {
      fromStream.CopyTo(toStream);
    }
  }
}

もちろん、C#・VBともに入れ子にして記述することも出来ます。

解放対象のオブジェクトを入れ子にしたusingステートメントで指定する
using System;
using System.IO;

class Sample {
  static void Main()
  {
    using (Stream fromStream = File.OpenRead("source.txt")) {
      using (Stream toStream = File.OpenWrite("dest.txt")) {
        fromStream.CopyTo(toStream);
      }
    }
  }
}
解放対象のオブジェクトを入れ子にしたusingステートメントで指定する
Imports System
Imports System.IO

Class Sample
  Shared Sub Main()
    Using fromStream As FileStream = File.OpenRead("source.txt")
      Using toStream As FileStream = File.OpenWrite("dest.txt")
        fromStream.CopyTo(toStream)
      End Using
    End Using
  End Sub
End Class

StreamクラスやCopyToメソッドについてはストリームの基本とStreamクラスを参照してください。

usingステートメントとIDisposableを使った終了処理のラップ

usingステートメントと終了処理をラップしたIDisposableを組み合わせて使うことにより、終了処理の記述をシンプルで確実なものにすることが出来ます。 例として、BeginXXX/EndXXXやOpenXXX/CloseXXXのように必ずペアで呼び出す必要があるメソッドを考えます。 BeginXXX, OpenXXXの呼び出しをコンストラクタ、EndXXX, CloseXXXの呼び出しをDisposeメソッドで行うようにしたラッパークラスを用意し、usingステートメントと組み合わせて使うことでtry-finallyを使うよりも終了処理を簡単に記述できるようになります。

例えば次のようなクラスがあった場合を想定します。

BeginXXXとEndXXXをペアで呼び出す必要があるクラスと、それを使用したコード
using System;

class Operation {
  // 何らかの操作を開始するメソッド
  public void BeginOperation()
  {
  }

  // 開始した操作の終了処理を行うメソッド
  public void EndOperation()
  {
  }
}

class Sample {
  static void Main()
  {
    Operation o = new Operation();

    try {
      o.BeginOperation();
    }
    finally {
      // BeginOperationを呼び出した後は必ずEndOperationを呼びだして終了処理を行う必要があるとする
      o.EndOperation();
    }
  }
}

このクラスをラップしたクラスを用意して、BeginXXXをコンストラクタ、EndXXXをDisposeメソッドから呼び出すようにすると、usingステートメントを使って次のように記述することができるようになります。

ペアで呼び出す必要があるメソッドをIDisposableでラップした例
using System;

class Operation {
  // 何らかの操作を開始するメソッド
  public void BeginOperation()
  {
  }

  // 開始した操作の終了処理を行うメソッド
  public void EndOperation()
  {
  }
}

// BeginOperationとEndOperationの呼び出しをラップするクラス
class OperationWrapper : IDisposable {
  private Operation o;

  public OperationWrapper()
  {
    o = new Operation();
    o.BeginOperation();
  }

  public void Dispose()
  {
    if (o != null) {
      o.EndOperation();
      o = null;
    }
  }
}

class Sample {
  static void Main()
  {
    // BeginOperationとEndOperationの呼び出しを隠蔽しつつ、確実に終了処理を行えるようになる
    using (var wrapper = new OperationWrapper()) {
    }
  }
}

以下の例は、BitmapクラスのLockBits/UnlockBitsメソッドの呼び出しをラップするクラスBitmapLockを作成し、usingステートメントで使用できるようにしたものです。 この例では、画像ファイルorigin.pngを読み込み、色を反転した結果をnegated.pngとして保存しています。

LockBits/UnlockBitsの呼び出しをラップしてusingステートメントで使えるようにする例
using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.Runtime.InteropServices;

// Bitmap.LockBits/UnlockBitsの処理をラップしたクラス
class BitmapLock : IDisposable {
  private Bitmap bitmap;
  private BitmapData data;

  public BitmapLock(Bitmap bitmap, Rectangle rect, ImageLockMode mode, PixelFormat format)
  {
    this.bitmap = bitmap;
    this.data = bitmap.LockBits(rect, mode, format);
  }

  public void Dispose()
  {
    if (data != null) {
      bitmap.UnlockBits(data);

      data = null;
      bitmap = null;
    }
  }

  // ラインyの先頭のポインタを取得する
  public IntPtr GetScanLine(int y)
  {
    if (data == null) throw new ObjectDisposedException(GetType().FullName);

    return new IntPtr(data.Scan0.ToInt32() + y * data.Stride);
  }
}

class Sample {
  static void Main()
  {
    using (Bitmap bitmap = (Bitmap)Image.FromFile("origin.png")) {
      Rectangle rect = new Rectangle(0, 0, bitmap.Width, bitmap.Height);

      // 画像全体のピクセルを読み書きできるようにロックする
      using (BitmapLock locked = new BitmapLock(bitmap, rect, ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb)) {
        for (int y = 0; y < rect.Height; y++) {
          IntPtr line = locked.GetScanLine(y);

          for (int x = 0; x < rect.Width; x++) {
            // 1ピクセル分読み込み
            int col = Marshal.ReadInt32(line, x * 4);

            // 反転した値を書き込む
            Marshal.WriteInt32(line, x * 4, ~col);
          }
        }
      }

      bitmap.Save("negated.png", ImageFormat.Png);
    }
  }
}
LockBits/UnlockBitsの呼び出しをラップしてusingステートメントで使えるようにする例
Imports System
Imports System.Drawing
Imports System.Drawing.Imaging
Imports System.Runtime.InteropServices

' Bitmap.LockBits/UnlockBitsの処理をラップしたクラス
Class BitmapLock
  Implements IDisposable

  Private bitmap As Bitmap
  Private data As BitmapData

  Public Sub New(ByVal bitmap As Bitmap, ByVal rect As Rectangle, ByVal mode As ImageLockMode, ByVal format As PixelFormat)
    MyClass.bitmap = bitmap
    MyClass.data = bitmap.LockBits(rect, mode, format)
  End SUb

  Public Sub Dispose() Implements IDisposable.Dispose
    If Not data is Nothing Then
      bitmap.UnlockBits(data)

      data = Nothing
      bitmap = Nothing
    End If
  End Sub

  ' ラインyの先頭のポインタを取得する
  Public Function GetScanLine(ByVal y As Integer) As IntPtr
    If data Is Nothing Then Throw New ObjectDisposedException(Me.GetType().FullName)

    Return New IntPtr(data.Scan0.ToInt32() + y * data.Stride)
  End Function
End Class

Class Sample
  Shared Sub Main()
    Using bitmap As Bitmap = DirectCast(Image.FromFile("origin.png"), Bitmap)
      Dim rect As New Rectangle(0, 0, bitmap.Width, bitmap.Height)

      ' 画像全体のピクセルを読み書きできるようにロックする
      Using locked As New BitmapLock(bitmap, rect, ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb)
        For y As Integer = 0 To rect.Height - 1
          Dim line As IntPtr = locked.GetScanLine(y)

          For x As Integer = 0 To rect.Width - 1
            ' 1ピクセル分読み込み
            Dim col As Integer = Marshal.ReadInt32(line, x * 4)

            ' 反転した値を書き込む
            Marshal.WriteInt32(line, x * 4, Not col)
          Next
        Next
      End Using

      bitmap.Save("negated.png", ImageFormat.Png)
    End Using
  End Sub
End Class