確実な終了処理を行うには?.NET TIPS

» 2003年05月23日 05時00分 公開
[川俣晶(http://www.autumn.org/)株式会社ピーデー(http://www.piedey.co.jp/)]

この記事は会員限定です。会員登録(無料)すると全てご覧いただけます。

「.NET TIPS」のインデックス

連載目次

 .NET Frameworkは、ガベージ・コレクションの機能により、使用済みのメモリ領域を自動的に回収する。それが単なるメモリ領域なら、使用可能なメモリ容量に余裕がある限り、回収されずに放置されるケースがあってもそれほど問題とはならない。しかし、極めて限られた資源や、特別な特徴を持った資源は、使い終わったらすぐに解放してやらねば不都合が起きる場合がある。その1つの例は、ファイルである。ファイルを開いて読み書きすることは容易であるが、.NET Frameworkで何のオプションも指定せずにファイルを開くと排他的な共有モードになるため、それを閉じるまでそのファイルにアクセスすることができなくなる。つまり、ファイルの読み書きが終了しても閉じないで放置すると、ほかの用途で開けないという弊害が発生する。

 以下はファイルを閉じないことにより不都合が生じる例である。この場合、OpenTextメソッドでファイルを開こうとしたときに例外が発生してしまい、ファイルを開くことができない。

StreamWriter writer = File.CreateText(@"c:\sample.txt");
writer.WriteLine("文字列を追加しています。");

StreamReader reader = File.OpenText(@"c:\sample.txt");
Console.WriteLine(reader.ReadLine());

 この問題に対処する最も安易な解決策は、ファイルを閉じるコードを追加することである。

StreamWriter writer = File.CreateText(@"c:\sample.txt");
writer.WriteLine("文字列を追加しています。");
writer.Close();

StreamReader reader = File.OpenText(@"c:\sample.txt");
Console.WriteLine(reader.ReadLine());

 これでプログラムは例外を投げないで動くようになるが、適切な対処方法とはいえない。なぜなら、CreateTextメソッドを実行した後で、かつ、Closeメソッドが実行される前に例外などが起こって処理が中断すると、ファイルが閉じられないまま処理が進行する可能性があるためだ。

 これに対処するには、一般的には例外処理のfinally構文を用いて、以下のように記述する。これにより途中で意図しない例外が発生しても、確実にCloseメソッドが呼び出されるようになる。

StreamWriter writer = File.CreateText(@"c:\sample.txt");
try {
  writer.WriteLine("文字列を追加しています。");
} finally {
  writer.Close();
}

StreamReader reader = File.OpenText(@"c:\sample.txt");
try {
  Console.WriteLine(reader.ReadLine());
} finally {
  reader.Close();
}

 しかし、C#の場合は、別の方法がある。以下のように記述しても、ファイルを確実に閉じることができる。

using (StreamWriter writer = File.CreateText(@"c:\sample.txt")) {
  writer.WriteLine("Sample");
}

using (StreamReader reader = File.OpenText(@"c:\sample.txt")) {
  Console.WriteLine( reader.ReadLine());
}

 これには、.NET Frameworkクラス・ライブラリのIDisposableインターフェース(System名前空間)と、C#のusingステートメントが関係している。StreamWriter/StreamReaderクラスは、IDisposableインターフェイスを実装しているので、このコードによって確実にファイルを閉じることが実現できているのである。

IDisposableインターフェイスとは何か?

 冒頭でも述べたように、.NET Frameworkはメモリ管理を自動的に行う。しかし、これにより確保したメモリがいつ解放されるかはガベージ・コレクタ次第である。必然的にデストラクタやFinallizeメソッドが呼ばれるタイミングも予測できない。それでは困るというニーズのために用意されたのが、IDisposableインターフェイスである。このインターフェイスは、Disposeメソッドだけを定義している。使い終わったら確実に資源を解放する処理が必要なクラスは、このインターフェイスを実装して、解放処理を記述するのが.NET Frameworkでのお約束である。

 以下は、IDisposableインターフェイスを実装したクラスの例である。

public class SampleClass : IDisposable {
  public SampleClass() {
    Console.WriteLine("資源を確保します。");
  }
  public void Dispose() {
    Console.WriteLine("資源を解放します。");
  }
}

 このように、IDisposableインターフェイスを実装するすべてのクラスではDisposeメソッドを定義している。IDisposableは普通のインターフェイスであり、Disposeも普通のメソッドであるため、特別な構文などは必要ない。.NET FrameworkのクラスでこのIDisposableインターフェイスを実装しているクラスは、リファレンス・マニュアルのIDisposableインターフェイスの項目で列挙されている。

 さて、オブジェクトが破棄されるときに呼び出されるデストラクタと異なり、Disposeメソッドは単なるメソッドであるため、何もしなければ永遠に呼び出されることはない。最も基本的な使い方は、以下のように明示的にDisposeメソッドを呼び出す方法である。

SampleClass sample1 = new SampleClass();
// 何かの処理
sample1.Dispose();

Copyright© Digital Advantage Corp. All Rights Reserved.

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。