何となく Blog by Jitta
Microsoft .NET 考

目次

Blog 利用状況
  • 投稿数 - 761
  • 記事 - 18
  • コメント - 36373
  • トラックバック - 222
ニュース
  • IE7以前では、表示がおかしい。div の解釈に問題があるようだ。
    IE8の場合は、「互換」表示を OFF にしてください。
  • 検索エンジンで来られた方へ:
    お望みの情報は見つかりましたか? よろしければ、コメント欄にどのような情報を探していたのか、ご記入ください。
It's ME!
  • はなおか じった
  • 世界遺産の近くに住んでます。
  • Microsoft MVP for Visual Developer ASP/ASP.NET 10, 2004 - 9, 2011
広告

記事カテゴリ

書庫

日記カテゴリ

ギャラリ

その他

わんくま同盟

同郷

 

ネタもと: 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
コメント