まえがき
File.ReadLinesは 遅延評価(IEnumerable) を利用し、1行ずつストリームで処理する。
foreachで全行読み込めば自動でDisposeされる。
使い方を誤るとファイルが開きっぱなしになり、リソースリークを引き起こす可能性がある。
そもそも、なぜストリームを閉じる必要があるのか?
C# に限らず、多くのプログラミング言語ではファイルを開いたら、明示的に閉じることが推奨される。理由は以下の通り:
-
ファイルハンドルの枯渇
- OS には 一度に開けるファイルの数に上限 がある。(Windows では通常
2048
、Linux ではulimit -n
で確認できる) -
File.ReadLines
はStreamReader
を内部で使っているため、適切に閉じないとファイルハンドルが解放されない。 - たとえば、長時間動作するアプリが
File.ReadLines
を繰り返し使い、ストリームを閉じないと、最終的に 新たなファイルが開けなくなる。
- OS には 一度に開けるファイルの数に上限 がある。(Windows では通常
-
ロックが解除されない
-
File.ReadLines
を使ってストリームが開いたままだと、Windows では 他のプロセスがそのファイルを編集・削除できなくなる。 - たとえば、Excel で CSV ファイルを開いた状態で編集できないのと同じ。
-
ReadLines
を途中で止めた場合、プログラムを終了するまでファイルがロックされたまま になり、別のプロセスがアクセスできなくなる。
-
-
メモリリーク(厳密にはハンドルリーク)
- ガベージコレクション(GC)はメモリの解放はするが、OS のファイルハンドルを解放しない。
- そのため、明示的に
Dispose
しないと、使われなくなったStreamReader
のインスタンスがGCに回収されるまで、無駄にOSリソースを消費し続ける。
実際のリソースリークの例
以下のようなコードがあるとする。
void ProcessFiles()
{
for (int i = 0; i < 1000; i++)
{
var lines = File.ReadLines("example.txt");
foreach (var line in lines.Take(5)) // 最初の5行だけ取得
{
Console.WriteLine(line);
}
}
}
問題点:
-
File.ReadLines("example.txt")
はStreamReader
を開くが、閉じられないままProcessFiles()
のループを繰り返す。 - 1000回繰り返すうちに、1000個のストリームが開きっぱなし になり、最終的に OS から「ファイルを開けない」というエラー (
IOException: The process cannot access the file
) が発生する可能性がある。
解決策:using
を使う
明示的にストリームを閉じるためには、以下のように GetEnumerator()
を using
で囲む。
void ProcessFiles()
{
for (int i = 0; i < 1000; i++)
{
using (var lines = File.ReadLines("example.txt").GetEnumerator())
{
int count = 0;
while (lines.MoveNext() && count < 5)
{
Console.WriteLine(lines.Current);
count++;
}
} // ここで Dispose() が呼ばれるのでストリームが閉じる
}
}
こうすることで、ループごとにストリームが確実に閉じられるので、OSのファイルリソースが枯渇することはない。
「開きっぱなし」が問題になる具体例
例①:別のプロセスがファイルを削除できない
var lines = File.ReadLines("example.txt");
foreach (var line in lines.Take(5))
{
Console.WriteLine(line);
}
// 別のプロセス(例:エクスプローラーやバッチ処理)で `example.txt` を削除しようとすると失敗する
File.Delete("example.txt"); // IOException: ファイルが別のプロセスで使用中です
File.ReadLines
の列挙が終わっていないため、ファイルがロックされたままになっている。
例②:ファイルを開きすぎて例外が発生
for (int i = 0; i < 10000; i++)
{
var lines = File.ReadLines("example.txt"); // ストリームを開くが閉じない
foreach (var line in lines.Take(1))
{
Console.WriteLine(line);
}
}
しばらくすると、次のエラーが出る可能性がある。
System.IO.IOException: The process cannot access the file 'example.txt' because it is being used by another process.
OSが「もうこれ以上ファイルを開けない!」とエラーを出す。
まとめ
状況 | ファイルが閉じる? | リスク |
---|---|---|
foreach (var line in File.ReadLines("file.txt")) ですべての行を読む |
✅ 自動的に閉じる | 問題なし |
File.ReadLines("file.txt").Take(5) のように一部の行だけ読む |
❌ ストリームが閉じない | ファイルロック、ハンドルリークの可能性 |
ループのたびに File.ReadLines("file.txt") を呼ぶ |
❌ ストリームが閉じない | 大量のファイルハンドルリーク、エラー発生 |
適切な対応
✅ すべての行を読むことで、自動でDisposeする
✅ 一部の行だけ読むなら using
で Dispose()
する
✅ usingを使えない場合は、例外処理を try-finally
でしっかり行う
これを意識すれば、ファイルを開きっぱなしにすることなく、安全に File.ReadLines
を使える。