ファイルや周辺機器などのリソース(OSが管理している資源)を使用する場合、 まずリソースを使用する権利を取得し、 リソースに対する操作(ファイルの読み書きなど)を行った後、 リソース使用権を破棄する必要があります。
メモリは .NET Framework のガーベジコレクション機能が自動的に管理していて、 プログラマが明示的に破棄してやる必要はないのですが、 ファイルなどはガーベジコレクションの管理対象外で、 明示的な破棄が必要です。
リソースの破棄を怠ると操作が正しく完了しなかったり、 他のプログラムからそのリソースを使用できなくなったりします。 (例えば、ファイルにロックが掛かったままになって、ファイルの読み書きがしばらくできなくなったり。) そのため、リソースの破棄は確実に行う必要があるのですが、 これは意外に面倒な作業だったりします。
例えば、ファイルの読み書きを行う場合、 まずファイルを開いて、読み書きを行った後、ファイルを閉じる必要があります。 以下に簡単な例を示します。
using System; using System.IO; class DisposeTest { static void Main(string[] args) { FileStream reader = new FileStream(args[0], FileMode.Open); // 先頭のNバイトを読み出して画面に表示 const int N = 32; byte[] buf = new byte[N]; reader.Read(buf, 0, N); for (int i = 0; i < N; ++i) { Console.Write("{0,4}", (int)buf[i]); if (i % 8 == 7) Console.Write('\n'); } reader.Close(); // ファイルを閉じる(リソースの破棄) } }
この例のようなリソース破棄の仕方には実は問題があります。
この例のコードでは例外が発生したときに Close メソッドが呼ばれないため、
リソースの開放が出来なくなります。
例外が発生した場合にも Close メソッドが呼ばれるようにするためには、
以下のように try-catch-finally 文を用います。
using System; using System.IO; class DisposeTest { static void Main(string[] args) { FileStream reader = new FileStream(args[0], FileMode.Open); try { // 先頭のNバイトを読み出して画面に表示 const int N = 32; byte[] buf = new byte[N]; reader.Read(buf, 0, N); for (int i = 0; i < N; ++i) { Console.Write("{0,4}", (int)buf[i]); if (i % 8 == 7) Console.Write('\n'); } } catch (Exception) { // 例外処理を行う } finally { // 例外が発生しようがしまいが finally ブロックは必ず実行される。 // リソースの破棄は finally ブロックで行う。 if (reader != null) reader.Close(); } } }
リソースの破棄の手順をまとめると以下のようになります。
(ただし、Resource はリソース管理用クラスで、
Dispose メソッドによりリソースの破棄を行うものとする。)
Resource r = new Resource(); try { リソースに対する操作 } finally { if(r != null) r.Dispose(); }
リソースの破棄は必ずこの手順で行います。 しかし、毎回同じ手順を繰り返すのは面倒です。 そこで、C#ではこの手順を自動的に行ってくれる構文が用意されています。 この構文は using 文と呼ばれ、以下のようにして用います。
using(Resource r = new Resource()) { リソースに対する操作 }
using 文を用いると、
コンパイラが自動的に上述のリソース破棄用のコードに展開してくれます。
ただし、using 文で使うリソース管理用クラスは
System.IDisposable インターフェース
を実装している必要があります。
(FileStream などのクラスライブラリ中のクラスは System.IDisposable インターフェースを実装しています。)
using 文を用いて上述の例を書き直したものを以下に示します。
using System; using System.IO; class DisposeTest { static void Main(string[] args) { using (FileStream reader = new FileStream(args[0], FileMode.Open)) { // 先頭のNバイトを読み出して画面に表示 const int N = 32; byte[] buf = new byte[N]; reader.Read(buf, 0, N); for (int i = 0; i < N; ++i) { Console.Write("{0,4}", (int)buf[i]); if (i % 8 == 7) Console.Write('\n'); } } } }
ちなみに、using() の中身は変数宣言だけではなく、式にすることもできます。
using(式) { リソースに対する操作 }
これで、以下のようなコードと同等な処理になります。
using(IDisposable r = 式) { リソースに対する操作 }
Resource r = 式; try { リソースに対する操作 } finally { if(r != null) r.Dispose(); }
例えば、以下のジェネリックスを使ったメソッドのように、 T が IDispose を実装している時だけ Dispose を呼び出したい場合などに便利です。
static void GenericMethod<T>(T obj) { using (obj as IDisposable) { obj に対する操作 } }