LoginSignup
12
10

More than 5 years have passed since last update.

using 文と IDisposable とデストラクタと

Posted at

はじめに

プログラムを組んでいると、ファイルやネットワーク接続などのリソースを安全に開放したい場面がよくあります。
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 文を使うことで、もう少し読みやすいコードにできそうです。

12
10
3

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
12
10