ネタもと: Insider.NET 会議室 SQLparameterとトランザクション
このスレッドというか、なんとなく、
「IDisposableを実装している = Disposeを実行しなければ」
という風潮があるようで。
むしろ、
「IDisposableを実装している = GCが解放してくれる」
ってのはダメかなぁ、、、
「すぐにリソースを解放して再利用できる状態にする必要がある。」
もの以外はですが。
.NET Framework で扱うリソースには、大きく分けて2種類あります。.NET Framework アプリケーションの土台(つまり CLR)が管理する「マネージドリソース(GC ヒープ)」と、管理しない「アンマネージドリソース」です。IDisposable インターフェイスは、「アンマネージドリソースを使うことを、使用者に知らせる」ことを目的とします。そして、GC によって解放されるのは「マネージドリソース」です。言い方を変えると、CLR によって管理されているから「マネージドリソース」であって、CLR が管理しない「アンマネージドリソース」を管理するのは使用者(開発者)です。
確かに、マイクロソフトから提供されているクラスについては、GC がマネージドリソースを回収するときに、アンマネージドリソースの解放も行われます(「回収」と「解放」の言葉を使い分けていることに注意)。しかし、それは、そのように実装されているからです。自分で実装するクラスについては、そのように実装しなければ、GC が動作するタイミングでも解放してもらえません。また、GC が回収を開始するのは、マネージドリソースが足りなくなったときです。アンマネージドリソースが足りなくても、動作しません。よって、「アンマネージドリソースのリーク」により、アプリケーションの動作が継続できなくなる可能性があります。このことから、「使用が終わったら Dispose」は、安全な書き方であるといえます。
使用者が、使い終わったら Dispose をコールしてくれることを期待しながら、そうしてくれなかったときにもアンマネージドリソースを解放できる、比較的安全な書き方について、説明します。
public class NotReleasable {
private System.IO.MemoryStream stream;
public NotReleasable() {
stream = new System.IO.MemoryStream();
}
}
このクラスは、IDisposable を実装していないので、このクラスを書いた人(あるいはソースを見た人)にしか、アンマネージドリソース(MemoryStream 内にあるファイルデスクリプタ)を使用していることがわかりません。そして、ここで割り当てているリソースは、解放するルーチンが存在しないので、確実にリークします。
では、修正します。
public class NotReleasable : IDisposable {
private System.IO.MemoryStream stream;
public NotReleasable() {
stream = new System.IO.MemoryStream();
}
public void Dispose() {
if (stream != null) {
stream.Close();
stream = null;
GC.SuppressFree(this);
}
}
}
IDispose インターフェイスを実装し、使用者にアンマネージドリソースを使用することを知らせました。Dispose メソッドを実装し、その中で解放を行います。なお、このクラスでは GC.SuppressFree を呼び出しているため、stream を使うようなメソッドでは、ObjectDisposedException をスローする必要があります。
このコードでも、やはりリークする可能性が存在します。使用者が Dispose を必ずコールするとは限らないからです。そのようなとき、このコードでは、GC は Dispose を呼び出してくれません。次のコードで確認してください。
[STAThread]
static void Main() {
ClassA a = new ClassA();
Console.WriteLine(a.ToString());
}
public class ClassA : IDisposable {
public ClassA() {
Console.WriteLine("コンストラクタ");
}
#region IDisposable メンバ
public void Dispose() {
Console.WriteLine("Dispose");
}
#endregion
}
コンストラクタは呼ばれ、コンソールにメッセージが表示されますが、Dispose は呼び出されません。GC の役目は参照されていないマネージドリソースを回収して再利用できるようにすることであって、Dispose メソッドを呼び出すことではないのです。
では、アンマネージドリソースが確実に解放されるようにするには、どうすればいいでしょうか。マネージドリソースが回収されるときに、アンマネージドリソースの解放もしてくれるように、頼むしかありません。では、そのように修正します。
public class Releasable : IDisposable {
private System.IO.MemoryStream stream;
public Releasable() {
stream = new System.IO.MemoryStream();
}
public void Dispose() {
if (stream != null) {
stream.Close();
stream = null;
GC.SuppressFree(this);
}
}
~Releasable() {
Dispose();
}
}
ガーベッジコレクションで実行されるのは、ファイナライザです。C# では、デストラクタとして実装します。この中で、Dispose を呼び出すことで、アンマネージドリソースを解放します。こうすれば、遅くともガーベッジコレクションが実行されるときに、アンマネージドリソースを解放することが出来ます。検証コードを修正して、確認してください。
このように、最終的には必ず解放されるようにするためには、ファイナライザで解放を指示する必要があります。ファイナライザでは、無条件で Dispose を呼び出すようにします。その為、Dispose は、何度呼ばれても支障がないような書き方をする必要があります。しかし、ファイナライザが実行されるのは、GC による回収が行われるときです。GC による回収は、マネージドリソースが足りなくなりそうなときに行われます。言い方を変えれば、アンマネージドリソースが足りなくなっても、回収はされません。このことから、アンマネージドリソースが不足する、リークしている状態となります。
IDisposable インターフェイスは、クラスの使用者に「このクラスはアンマネージドリソースを使用しています。使用後はすみやかに Dispose メソッドを呼び出して、アンマネージドリソースを解放してください」ということを知らせる、いわば警告文です。不必要に実装したり、実装したクラスを Dispose しないまま放置したりすることのないように、気をつけましょう。
なお、デストラクタはコンパイラによって置き換えが行われます。上のデストラクタは、次のように置き換えられます。したがって、デストラクタに親クラスの Dispose を確実に呼ぶような動作を書き込むことは不要です。
protected override void Finalize() {
try {
Dispose();
} finally {
base.Finalize();
}
}
投稿日時 : 2006年2月21日 21:55