C# - ファイル操作メモ

そういえばStreamReader/Writerを直接呼び出すだけだったなので、基本を確認していなかった。
ということで、

メモ

using System.IO;

.net core でプロジェクト作ると using Systemだけなので、
忘れずに追加する。

ファイルの扱い

文字列(string)を対象にstaticメソッドで扱うFile/Directoryと、
インスタンス作って扱うFileInfo/DirectoryInfoというのがある。
前者はあくまでも文字列なので、staticメソッド内でのみファイル扱い。
後者はインスタンス化時点からファイル扱い、かな?
その性質上パフォーマンス的な差異があるようだが、

混ぜて使わない、常に同じ書き方をしたい、とするなら
FileInfo/DirectoryInfoのほうを使えばよいのかな、と思う。

DirectoryInfoから階層を辿ったり、検索したりができる。

var di = new DirectoryInfo(@"/path/to/working/directory");
foreach (var fi in di.EnumerateFiles(@"*.cs", SearchOption.AllDirectories))
{
    Console.WriteLine(fi.FullName);
}

FileInfoでOpen(/OpenRead/OpenWrite)するとFileStreamが得られる。

var fi = new FileInfo(@"Program.cs");
using (var fs = fi.OpenRead()) {}

このストリームはそのまま使えるし、ファイル種に合わせて別のストリームでラップしても良い。

テキストファイル読み書き

StreamReaderとStreamWriterでする。
エンコード指定できる。デフォルトは UTF-8。

末尾まで来たことはEndOfStreamプロパティで判断できる。

var fi = new FileInfo(@"Program.cs");

using (var fs = fi.OpenRead())
using (var sr = new StreamReader(fs))
{
    while (!sr.EndOfStream)
    {
        Console.WriteLine(sr.ReadLine());
    }
}

ただ、読み取りメソッドは末尾まで到着するとnullを返したりなど応答が変わるので、それで終わりを判断することが多いかもしれない。

バイナリファイル読み書き

BinaryReaderとBinaryWriterでする。
型ごとのRead/Writeメソッドが用意されている。
Read時はReadByteなど、メソッド名が異なる。
Writeは引数の型で判断される(オーバーロード)。

var fi = new FileInfo(@"data.bin");

using (var fs = fi.OpenWrite())
using (var bw = new BinaryWriter(fs))
{
    bw.Write((Byte)0x10);
    bw.Write((Int32)100);
}

using (var fs = fi.OpenRead())
using (var br = new BinaryReader(fs))
{
    Console.WriteLine(br.ReadByte());
    Console.WriteLine(br.ReadInt32());
}

リトルエンディアン強制らしい。
データはメモリ上で解析・構築することにして、バイト配列で一気読み書きする方が簡単なのかもしれない。
エンディアンの変換は自分で書くか、HostToNetworkOrder/NetworkToHostOrder使うか、
あるいはSystem.Buffers.Binary.BinaryPrimitives(.NET Core v2.1から追加された)を使うか。
BinaryPrimitivesであれば、バイト配列の所定の位置に好きなエンディアンで書き込める。

var data = new byte[8];
BinaryPrimitives.WriteInt32LittleEndian(data.AsSpan().Slice(0, 4), 1732584193);
BinaryPrimitives.WriteInt32BigEndian(data.AsSpan().Slice(4, 4), 1732584193);
// data = {0x01, 0x23, 0x45, 0x67, 0x67, 0x45, 0x23, 0x01}

もちろん、バイト配列の所定の位置から好きなエンディアンで読み込める。

var data = new byte[] {0x01, 0x23, 0x45, 0x67, 0x67, 0x45, 0x23, 0x01};
var little = BinaryPrimitives.ReadInt32LittleEndian(data.AsSpan().Slice(0, 4));
var big = BinaryPrimitives.ReadInt32BigEndian(data.AsSpan().Slice(4, 4));
// big == little

See also