Visual Studio Community 2015(以下、VS2015 と書きます)には Dispose パターンに準拠したコードの骨組みを自動生成する機能が追加されています。
上の画像がそれで、IDisposable とタイプすると電球マークが表示されるので、その横の▼印をクリックすると Disposeパターンを選択できるようになります。
ここで、[Dispose パターンを使ってインターフェイスを実装します]にマウスをポイントすると以下の画像(Dispose パターンの骨組みのコード)が表示され、クリックするとそのコードがクラスに挿入されます。
([... 明示的に ...]の方を選ぶと、Dispose メソッドが IDisposable.Dispose と明示的に実装されます。その場合、クラスインスタンスから Dispose には直接アクセスできませんので注意してください。明示的の使い道は調べてなくて不明です)
そういう機能が追加されていることを知ってました? 実は自分は最近まで知らなかったです。(汗) VS2010 にはその機能はありませんでしたので、VS2012 ~ VS2015 のどれかから追加されたようです。
Dispose パターンの実装については、Microsoft のドキュメント「Dispose メソッドの実装」などに書かれていますが、読んでもよく分かりませんでした。(笑)
(ちなみに、VS2015 で自動生成されるのは上の Microsoft のドキュメントの「Dispose パターンには 2 種類あります」以下に書いてある後者の Object.Finalize メソッドをオーバーライドする方です)
VS2015 で自動生成されたコードと、ネットの情報「確保したリソースを忘れずに解放するには?」や「C# のファイナライザ、Dispose() メソッド、IDisposable インターフェースについて」などを読んで、やっと Dispose パターンを理解できたような気がします。
Dispose パターンの実装が必要なのは、アンマネージドリソースを使用するクラスのみです。アンマネージドリソースの種類で一般的なのは、ファイル、ウィンドウ、ネットワーク接続、データベース接続などのオペレーティングシステムリソースをラップしたオブジェクトです。
.NET Framework のクラスライブラリの中にも、例えば FileStream のようにファイルを開くために Windows API を呼び出してファイルハンドルを保持するというように、アンマネージドリソースを利用するものがあります。
そういうクラスは、IDisposable インターフェイスを継承して Dispose パターンを使った実装がされていて、Dispose メソッドでアンマネージドリソースを開放できるようになっているはずです。
ソースを見たわけではないので、.NET Framework のクラスライブラリの全部が全部そうなのかは分かりませんが、Dispose メソッドを実装しているクラスは内部でアンマネージドリソースを使っていると考えて、そのオブジェクトが使用されなくなった時点で Dispose メソッドを呼び出すことを基本とすべきと思います。
自分で作るカスタムクラスの場合、マネージドリソースしか保持しない場合は Dispose パターンの実装は不要です。必要なのはアンマネージドリソースを保持する場合のみですが、それには以下のケースがあると思います。
-
Dispose パターンを実装した .NET のクラスのインスタンスを保持している。
-
クラス内でアンマネージドリソースを取得し、それを保持している。
VS2015 のウィザードが生成する Dispose パターンの骨組みのコードを利用して、上記 1 および上記 1 + 2 のケースでコードを実装してみます。
上記 1 のケース
Dispose パターンを実装した .NET のクラスの例として DataSet を使いました。
public class DisposableSample : IDisposable
{
private DataSet myDataSet;
public DisposableSample()
{
myDataSet = new DataSet();
// ・・・中略・・・
}
private bool disposedValue = false; // 重複呼出の検出用
protected virtual void Dispose(bool disposing)
{
if (!disposedValue)
{
if (disposing)
{
// .NET のオブジェクトを解放
myDataSet.Dispose();
}
disposedValue = true;
}
}
public void Dispose()
{
Dispose(true);
}
// ・・・中略・・・
}
上記 1 + 2 のケース
アンマネージド(= GC では解放できない)リソースの例として SqlConnection で接続プールから取得する接続を使いました。接続プールはデフォルトで有効で、SqlConnection を Open すると接続プールから接続を取得してきます。
public class DisposableSample : IDisposable
{
private DataSet myDataSet;
private SqlConnection connection;
public DisposableSample()
{
myDataSet = new DataSet();
// アンマネージドリソースを取得
string connString = ConfigurationManager.
ConnectionStrings["MyDB"].
ConnectionString;
connection = new SqlConnection(connString);
connection.Open();
// ・・・中略・・・
}
private bool disposedValue = false; // 重複呼出の検出用
protected virtual void Dispose(bool disposing)
{
if (!disposedValue)
{
if (disposing)
{
// .NET のオブジェクトを解放
myDataSet.Dispose();
}
// アンマネージドリソースを解放
// SqlConnection の Dispose と Close は同じ
connection.Dispose();
disposedValue = true;
}
}
// Dispose し忘れても、ガベージコレクタが働いたときに
// ファイナライザが呼ばれるので、そこでアンマネージド
// リソースを解放できるよう以下のコードを実装する。
// C++ のデストラクタの構文だが、C# ではこれによりフ
// ァイナライザをオーバーライドすることになるらしい
~DisposableSample()
{
Dispose(false);
}
public void Dispose()
{
Dispose(true);
// 上の Dispose(bool disposing) メソッドでアンマネー
// ジドリソースを解放した場合はファイナライザが実行
// されないようにする
GC.SuppressFinalize(this);
}
// ・・・中略・・・
}
上のコードのアンマネージドリソースの取得で、実際にこのような形で接続を保持するケースはないのかもしれませんが、他に具体例が思いつかなかったのでこうしてみました。
プログラマが Dispose() するコードを書くのを忘れた場合でもガベージコレクタは働きます。ガベージコレクタではアンマネージドリソースは開放できませんがファイナライザは呼び出されるそうです。なので、上の ~DisposableSample() でファイナライザをオーバーライドし、その中で Dispose(false) を呼び出してアンマネージドリソースを開放するというパターンを実装するそうです。
Dispose() メソッドが呼ばれ、それから Dispose(bool disposing) メソッドが呼ばれてアンマネージドリソースを解放した場合は GC.SuppressFinalize(this) を呼び出します。
ファイナライザの実行はパフォーマンスに大きな影響を与えるそうですが、GC.SuppressFinalize メソッドを呼び出しておくと、ファイナライザの呼び出しは行われなくなります。
Dispose パターンを実装済のクラスを継承してカスタムクラスを作成し+それに上記 1 のケースで追加したオブジェクトがある場合、追加したオブジェクトを Dispose するにはどうするかについては、別の記事「Dispose パターン (その 2)」に書きます。