この記事は会員限定です。会員登録(無料)すると全てご覧いただけます。
.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インターフェイスを実装しているので、このコードによって確実にファイルを閉じることが実現できているのである。
冒頭でも述べたように、.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();
しかし、これではDisposeメソッドが確実に呼ばれる保証がない。保証させようとtry構文を用いて以下のように書くことはできる(※1)。
SampleClass sample1 = new SampleClass();
try {
// 何かの処理
} finally {
sample1.Dispose();
}
しかし、このようにtry構文で資源を解放させるのなら、共通のIDisposableインターフェイスのありがたみは薄い。そこで出てくるのがusingステートメントである。
C#のソースコードの先頭には、必ずといってよいほど名前空間省略時のデフォルトを指定する「using System;」のようなコードが書かれている。以下本稿で「using」というキーワードについて説明するが、このusingステートメントは、名前空間を指定する前出の「using」とは異なるものであるので注意していただきたい。ここで取り上げるusingステートメントは確実な終了処理を行うためのものである。usingステートメントは以下のようにして使用する。
using (SampleClass sample1 = new SampleClass()) {
// 何かの処理
}
まず、usingに続く括弧内で、対象となるオブジェクトを指定する。この例では、SampleClassクラスのインスタンスを作成し、それをsample1という変数の初期値としている。ここで指定する値は、IDisposableインターフェイスを実装していなければならない。そして、それに続くブロックから何らかの理由で抜け出すときに、指定されたオブジェクトが持つDisposeメソッドが呼び出される。そのまま処理を終えて抜ける場合ももちろんDisposeメソッドは呼ばれる。それだけでなく、通常の処理がまったく不可能になるような極めて例外的な事態を除けば、returnステートメントでメソッドを抜けたり、例外が発生したりする場合など、ほとんどの場合でDisposeメソッドは呼び出される。この例でいえば、変数sample1が持っているSampleClassクラスのインスタンスのDisposeメソッドが呼び出される。
このコードは上記のtry構文を用いたコード(※1)とほぼ等価である。このことから分かるとおり、usingステートメントはを使用しなくても等価のコードを書くことはできる。しかし、コードをコンパクトに分かりやすく仕上げるには、ぜひとも活用したいステートメントであるといえるだろう。
カテゴリ:C# 処理対象:オブジェクト
使用キーワード:usingステートメント
使用ライブラリ:IDisposableインターフェース(System名前空間)
Copyright© Digital Advantage Corp. All Rights Reserved.