ファイル関係のプログラムを書いていて、ファイルをクローズしたくないというシチュエーションがあったので、調べてみた。
初めに
ファイル(Stream)関連を使う場合、using句を使い、処理区間を明確にし、処理終了後にDisposeが呼び出されるようにするのが一般的。
ただ、クラスやメソッド間をStreamでやり取りし、呼び出された側で、それをStreamWriterやStreamReaderなどで利用したい場合、通常では問題があった。
問題点
例えば以下のコード。
class Program {
static void Main(string[] args) {
string filename = @"hoge.txt";
using (var stream = File.Open(filename, FileMode.CreateNew)) {
using (var writer = new StreamWriter(stream)) {
writer.WriteLine("hogehoge");
}
Console.WriteLine(stream.Length);
}
}
}
5~7行目で、その前にオープンしていたStreamをStreamWriter形式で、開きなおした。
ただ、この例では、7行目でwriterがクローズされると同時に、streamもクローズされてしまった。
そのため、8行目のConsole.WriteLine(stream.Length);で既に閉じていると、例外が発生した。
たぶん、上記の様に、すでにオープンしているものを、再オープンするような実装は、どのオブジェクトで、最終的にリソースの管理がされているのかわからなくなるので、望ましくないのだと思う。
ただ、今回必要となったため、writer側でクローズされないようにするには、どうすればいいのか調査してみた。
調査結果
色々、検索してみたところ、StreamReader/WriterのコンストラクタにleaveOpenという引数があることが分かった。
これは、Disposeでクローズしないというフラグらしい。
以下のようなコードで試してみた。
class Program {
static void Main(string[] args) {
string filename = @"hoge.txt";
using (var stream = File.Open(filename, FileMode.CreateNew)) {
using (var writer = new StreamWriter(stream, Encoding.ASCII, 4096, true)) {
writer.WriteLine("hogehoge");
writer.Flush();
}
try {
File.Delete(filename);
}
catch (IOException ex) {
Console.WriteLine(ex.ToString());
}
Console.WriteLine(stream.Length);
}
try {
File.Delete(filename);
}
catch (IOException ex) {
Console.WriteLine(ex.ToString());
}
}
}
ファイルがオープンされている状況が継続しているのであれば、10行目のファイル削除ができないので、例外が発生するはず。
実際実施した場合、例外が発生した。また、その後のstream.Lengthでも正しく値が取り出せた。
大外のstreamの終了で、ファイルがクローズされているかどうかも、18行目のファイル削除で例外が発生せず、また、ファイルも削除されていたことから問題ないと認識できた。
注意点
- leaveOpenを使った場合、Close処理が行われていなかったため、using句から抜ける際に、writer側のバッファがフラッシュされていなかった。using句から抜ける前に、Fluhs()を個別に入れてあげる必要がある。
- コンストラクタでleaveOpenを指定する場合、エンコーディング、バッファサイズの指定が必要になる。
エンコーディングは、適時設定すればいいのだが、バッファサイズにはどれくらい必要なのだろうか。
調べたところ、現在のデフォルトは1024で、最大は4096だそうだ。
その他
MSDNのStreamWriter(Stream, Encoding, Int32, Boolean)の日本語説明に「円コーディング」と入っていたのがちょっと笑った。
他は、「エンコーディング」となっているのに、ここだけなぜ違う?
コメント