ファイルやDB接続等、.NET Frameworkの管理下にないリソース(unmanaged resource)を利用する際は、適切な後処理を行う必要がある。.NET Frameworkにおいて標準的に利用されるパターンは、IDisposable
インタフェースの実装である。
リソースの後処理は、原則Dispose
メソッドで行う。クラスの利用者がDispose
を呼び忘れた場合に備えて、ファイナライザでもunmanaged resourceの後処理を行うのが安全である。また、Dispose
とファイナライザは、いずれも実際のリソース解放処理を仮想メソッドとして定義して、派生クラスが必要に応じて振る舞いを変更できるようにするとよい。
派生クラスでは、必要に応じてリソース処理の仮想メソッドをオーバーライドする。さらに、自分自身のメンバーフィールドがunmanaged resourceである場合に限って、ファイナライザを実装する。いずれの場合でも、ベースクラスのリソース解放メソッドを呼ぶことを忘れないように。
IDisposable.Dispose()
メソッドの実装は、4つの仕事をする責務を負う。
- 全てのunmanaged resourceを解放する
- 全てのmanaged resourceを解放する(フックしないイベントも含む)
- オブジェクトがDispose済みであることを示すフラグをセットする。なお、publicメンバーは、Dispose後に呼ばれた場合には、このフラグを確認して、
ObjectDisposed
例外を投げる必要がある - ファイナライザの実行を抑制する。これには
GC.SuppressFinalize(this)
が利用できる。
IDisposable
の典型的な実装は以下のようになる。
public class MyResource : IDisposable
{
// Dispose済みか否かのフラグ
private bool _disposed;
// IDisposableの実装
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
// 実際の破棄処理を行う仮想メソッド
protected virtual void Dispose(bool isDisposing)
{
if (_disposed)
return;
if (isDisposing)
{
// managed resourceはDispose時のみ解放する
}
// unmanaged resourceは常に解放する
_disposed = true;
}
public void ExampleMethod()
{
if (_disposed)
throw new ObjectDisposedException(nameof(MyResource));
}
}
public class DerivedResource : MyResource
{
// Dispose済みフラグはクラスごとにもつ
private bool _disposed;
protected override void Dispose(bool isDisposing)
{
if (_disposed)
return;
if (isDisposing)
{
// managed resourceの解放
}
// unmanaged resourceの解放
base.Dispose(isDisposing);
_disposed = true;
}
}
なお、ファイナライザは、unmanaged resourceを解放する必要がある場合にのみ定義すべきである。GCは、ファイナライザの定義されたオブジェクトを、ファイナライザ実行待ちのキューに入れ、その後ファイナライザを実行してからオブジェクトを破棄する。そのため、ファイナライザを定義すると、ファイナライザがない場合よりもオブジェクトの生存期間が長くなり、パフォーマンス上の悪影響がある。
また、Dispose
では、リソースの解放以外の処理を実行してはいけない。