1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

C#のFile.ReadLinesの安全な使い方 - なぜストリームを閉じる必要があるのか

Last updated at Posted at 2025-03-01

まえがき

File.ReadLinesは 遅延評価(IEnumerable) を利用し、1行ずつストリームで処理する。
foreachで全行読み込めば自動でDisposeされる。
使い方を誤るとファイルが開きっぱなしになり、リソースリークを引き起こす可能性がある。

そもそも、なぜストリームを閉じる必要があるのか?

C# に限らず、多くのプログラミング言語ではファイルを開いたら、明示的に閉じることが推奨される。理由は以下の通り:

  1. ファイルハンドルの枯渇

    • OS には 一度に開けるファイルの数に上限 がある。(Windows では通常 2048、Linux では ulimit -n で確認できる)
    • File.ReadLinesStreamReader を内部で使っているため、適切に閉じないとファイルハンドルが解放されない。
    • たとえば、長時間動作するアプリが File.ReadLines を繰り返し使い、ストリームを閉じないと、最終的に 新たなファイルが開けなくなる
  2. ロックが解除されない

    • File.ReadLines を使ってストリームが開いたままだと、Windows では 他のプロセスがそのファイルを編集・削除できなくなる
    • たとえば、Excel で CSV ファイルを開いた状態で編集できないのと同じ。
    • ReadLines を途中で止めた場合、プログラムを終了するまでファイルがロックされたまま になり、別のプロセスがアクセスできなくなる。
  3. メモリリーク(厳密にはハンドルリーク)

    • ガベージコレクション(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する
一部の行だけ読むなら usingDispose() する
usingを使えない場合は、例外処理を try-finally でしっかり行う

これを意識すれば、ファイルを開きっぱなしにすることなく、安全に File.ReadLines を使える。

1
1
0

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
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?