.NET TIPS

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

株式会社ピーデー  川俣 晶
2003/05/23

 .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();

 しかし、これではDisposeメソッドが確実に呼ばれる保証がない。保証させようとtry構文を用いて以下のように書くことはできる(※1)。

SampleClass sample1 = new SampleClass();
try {
  // 何かの処理
} finally {
  sample1.Dispose();
}

 しかし、このようにtry構文で資源を解放させるのなら、共通のIDisposableインターフェイスのありがたみは薄い。そこで出てくるのがusingステートメントである。

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ステートメントはを使用しなくても等価のコードを書くことはできる。しかし、コードをコンパクトに分かりやすく仕上げるには、ぜひとも活用したいステートメントであるといえるだろう。End of Article

カテゴリ:C# 処理対象:オブジェクト
使用キーワード:usingステートメント
使用ライブラリ:IDisposableインターフェース(System名前空間)
 
この記事と関連性の高い別の.NET TIPS
テキスト・ファイルの内容を読み込むには?[C#、VB]
WPF:例外をまとめてトラップするには?[C#/VB]
[ASP.NET MVC]例外フィルタをカスタマイズするには?
[ASP.NET]ページから生成されたソース・コードを見るには?
適切に処理されなかった例外をキャッチするには?
このリストは、(株)デジタルアドバンテージが開発した
自動関連記事探索システム Jigsaw(ジグソー) により自動抽出したものです。
generated by

「.NET TIPS」

@IT Special

- PR -

TechTargetジャパン

Insider.NET フォーラム 新着記事
  • 第2回 簡潔なコーディングのために (2017/7/26)
     ラムダ式で記述できるメンバの増加、throw式、out変数、タプルなど、C# 7には以前よりもコードを簡潔に記述できるような機能が導入されている
  • 第1回 Visual Studio Codeデバッグの基礎知識 (2017/7/21)
     Node.jsプログラムをデバッグしながら、Visual Studio Codeに統合されているデバッグ機能の基本の「キ」をマスターしよう
  • 第1回 明瞭なコーディングのために (2017/7/19)
     C# 7で追加された新機能の中から、「数値リテラル構文の改善」と「ローカル関数」を紹介する。これらは分かりやすいコードを記述するのに使える
  • Presentation Translator (2017/7/18)
     Presentation TranslatorはPowerPoint用のアドイン。プレゼンテーション時の字幕の付加や、多言語での質疑応答、スライドの翻訳を行える
@ITメールマガジン 新着情報やスタッフのコラムがメールで届きます(無料)
- PR -

イベントカレンダー

PickUpイベント

- PR -

アクセスランキング

もっと見る

ホワイトペーパーTechTargetジャパン

注目のテーマ

Insider.NET 記事ランキング

本日 月間
ソリューションFLASH