[C#.NET] 書き込みモードで開かれたファイルを読み取る ReadLines メソッド

System.IO.File.ReadLines() で IIS のログファイルを読み取るコードを書いていたのですが、本番環境に適用してみると

別のプロセスで使用されているため、プロセスはファイル “ほげほげ” にアクセスできません

などと怒られてしまいました。

結論

結論から言うと下記のようなユーティリティメソッドを作って利用するのが手っ取り早いです。

この内容については後述します。例外処理等は適当なので、必要に応じて追加してください。

下記のような感じで使えます。

var lines = FileText.ReadLines(path)
    .Skip(2) // 2 行スキップ
    .Where(x => x.Contains("HOGE")) // HOGE を含んでいれば
    .Select(x => ConvertLine(x)); // なんか処理
foreach (var line in lines)
{
    Console.WriteLine(line);
}

原因

IIS はログファイルを書き込む際に当然ながらファイルロックをかけていますが、どうやら System.IO.File.ReadLines() は書き込みモードで開かれているファイルを読み込めないようです。

Microsoft の .NET Framework のソースコードを参照すると ReadLines は下記のように実装されています。

[ResourceExposure(ResourceScope.Machine)]
[ResourceConsumption(ResourceScope.Machine)]
public static IEnumerable<String> ReadLines(String path)
{
    if (path == null)
        throw new ArgumentNullException("path");
    if (path.Length == 0)
        throw new ArgumentException(Environment.GetResourceString("Argument_EmptyPath"), "path");
    Contract.EndContractBlock();

    return ReadLinesIterator.CreateIterator(path, Encoding.UTF8);
}

ReadLinesIterator.CreateIterator で作られたイテレーターが返されています。これをさらにたどってみると…

internal class ReadLinesIterator : Iterator<string>
{
    // ~中略~
    private static ReadLinesIterator CreateIterator(string path, Encoding encoding, StreamReader reader)
    {
        return new ReadLinesIterator(path, encoding, reader ?? new StreamReader(path, encoding));
    }
}

StreamReader が引数なしで作成されることがわかります。さらに StreamReader のコンストラクターを参照すると…

Stream stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read, DefaultFileStreamBufferSize, FileOptions.SequentialScan, Path.GetFileName(path), false, false, checkHost);

FileShare.Read となっています。この FileShare という列挙体は非常にわかりにくいのですが、すでにロックされているファイルを開くとき、下記のように動作します。

  • Read: 読み取りロックがかかっているファイルが開ける
  • ReadWrite: 読み取り書き込みロックがかかっているファイルが開ける
  • None: ロックされていないファイルのみ開ける

FileShare Enum (System.IO) | Microsoft Docs

これが Read になっているので書き込みロックのかかっているファイルが開けない、ということですね。

詳細

内容的には FileShare を渡せるようにしただけです。

メソッドは IEnumerable<string> を返すようにし、内部では yield return で一行ごとに返していきます。

このときファイルの終端 (EOF) をチェックするのに Peek() メソッドの戻り値をチェックしている例が多いですが、 EndOfStream プロパティをチェックするか、実装例のように ReadLine() の戻り値が null かどうかで確認するようにします。

SNSでもご購読できます。