はじめに
プログラムを組んでいると、ファイルやネットワーク接続などのリソースを安全に開放したい場面がよくあります。
C++ では、デストラクタでリソースを開放することにして、オブジェクトをブロック内のローカル変数にする方法が使えました。
class DocFile
{
public:
~DocFile() { Close(); } // デストラクタで片付ける
void Open() { ... }
void Close() { ... }
}
void MyFunc()
{
{
DocFile df;
df.Open();
... // df について、いろいろ
} // この位置で必ず DocFile df のデストラクタが呼ばれて、df.Close()が実行される。
}
C# では、オブジェクトの片付けのタイミングはガベージコレクタに任されており、C++ と同じ方法は使えません。その代わり、using 文が用意されています。
using (オブジェクト) 文
using の後ろの「文」を抜けるときに、括弧内のオブジェクトの片付けが行われます。
以下のコードは典型的な using 文の使い方です。
using 文の括弧の new で StreamReader
オブジェクトがリソースを摑みます。あとは、using 文から抜けたときに先程のオブジェクトがリソースを開放します。using 文からの抜け方は、正常終了でも return でも例外でもなんでもよく、リソースの解放をいちいち既述しなくても、確実な解放が保証されます。
using (StreamReader sr = new StreamReader(path)) {
while (!sr.EndOfStream) {
string line = sr.ReadLine();
if (...) return;
if (...) throw new Exception();
// いろいろ
}
} // ファイル入力が解放される
上記のように書いた場合、オブジェクト sr
は using 文を抜けた後は参照できなくなります。
このオブジェクトを他でも使いたいと思い、少し試してみました。
using 文
基本的なしくみ
using 文は、括弧内に IDisposable
インターフェースを実装したオブジェクトを要求します。IDisposable
は、メソッド void Dispose()
のみを持つインターフェースです。using 文から抜けるとき、オブジェクトの Dispose()
メソッドを必ず呼び出します。
通常、Dispose()
には片付け処理を記述してあるので、using を抜けた時に確実に片付けがおこなわれます。
オブジェクトはどこへいったか
括弧の中で変数を宣言した場合、using 文の外側ではその変数を参照できません。
using (DocFile df = new DocFile()) {
...
}
... // この位置ではもう df を参照できない
しかし、オブジェクトは(ガベージコレクタが片付けていなければ)まだ生きています。クラスのファイナライザ(~DocFile()
メソッド)を書いて、デバッガで止めてみるとよくわかります。
ということは、using 文の前で参照できるようにしておけば、using 文の後ろでも参照できるのでしょうか。
DocFile df = new DocFile();
using (df) {
...
} // df.Dispose() が呼ばれる
Console.Out.WriteLine(df.Path); // この位置でも df を参照できる!
予想通り、using 文の後ろでも参照できました。
Dispose()
メソッドも、using 文を抜けるときにきちんと呼ばれています。
オブジェクトで管理しなくてもよい
using 文の外側で作成したオブジェクトでも using 文に使えることがわかりました。
ということは、オブジェクトそのものではなく、リソースの取得と解放のみを using 文の対象にできます。
以下の例では、実際にファイルを開いている Open()
から Close()
までを using で管理しています。
class DocFile : IDisposable
{
public DocFile(string path) { ... }
public DocFile Open()
{
... // ファイルを開く処理
return this; // 自分自身を返すのがミソ
}
public void Close() { ... }
public void Dispose() { Close(); }
}
DocFile df = new DocFile(path);
if (df.IsUpdated()) {
using (df.Open()) { // Open() したオブジェクト df が using 文の対象
... // 何か処理
} // df.Dispose() が呼ばれる
}
Console.Out.WriteLine(df);
おわりに
今まで、using 文の括弧内でオブジェクトを new するものだ、と思い込んでいました。そのため、このオブジェクトを参照する処理が using のブロック内に詰め込まれ、ネストが深くて長いコードになっていました。
しかし、今回、using にはオブジェクトを渡せばよいことがわかりました。これからは、本当にリソースを使っている部分のみ using 文を使うことで、もう少し読みやすいコードにできそうです。