色んな事を書く

シンプルさを極めたエンジニアになりたい

C# で CSV ファイルの読み書きをする

CSV ファイルって何よ

rfc 4180 で定義されているファイル形式の事。CSV とは Comma Separated Values の頭文字をとったもの。 www.rfc-editor.org

ざっくりとファイルの中身は以下のように定義されてる。

  1. 各レコードは CRLF によって区切られた個別の行に配置される。
  2. 末尾のレコードは CRLF を含まなくても良い。
  3. ヘッダー行を含める事が出来る。
  4. 各レコードのフィールドは「,」で区切られる。末尾のフィールドは「,」を持っちゃはダメ。
  5. 各フィールドは「"」で囲んでも良い。囲まない場合、フィールドに「"」を含めちゃダメ。
  6. フィールドに「"」「CRLF」「,」を含める場合は、フィールドを「"」で囲まないといけない
  7. フィールドに「"」を含める場合、「"」で囲った上で別の「"」を使ってエスケープしないといけない。

7 が分かりにくいので例を残しておく。

"aaa""bb" // これは OK。aaa"bb というフィールドになる。
"aaa"bb"    // これは NG。"  の前にエスケープ用の " を置かないといけない。

といった定義が CSV ファイルにはある。 この定義を守ってファイルを取り扱うのは少々面倒なので CsvHelper っていうライブラリを使う。

CsvHelper

OSS です。

C# で定義した class を CSV のヘッダーとしてマップしたり、また CSV のフィールドを class にマップしたり出来ます。

また、RFC 4180 に準拠しているので、特に意識することなく 1 で紹介した形式を守ってくれます。システム間の互換性とか保ってくれますね。

RFC 4180 に準拠させたくない時は CsvMode を使う。CsvWriter とか CsvReader をインスタン化させる時に new CsvConfiguration に RFC4180 以外の CsvMode を渡す。 デフォは RFC4180 。

使い方は簡単で、Package Manger Console、もしくは .NET CLI Console で以下のコマンドを叩くだけ。

// Package Manager Console
PM> Install-Package CsvHelper

// .NET CLI Console
> dotnet add package CsvHelper

repository はこちらGitHub - JoshClose/CsvHelper: Library to help reading and writing CSV files

Document はこちらA .NET library for reading and writing CSV files. Extremely fast, flexible, and easy to use. | CsvHelper

CSV ファイルを一行ずつ読み込む

使い方は超絶簡単。

  1. StreamReader に読み込ませたい File Path を渡してインスタンス化。
  2. CsvReader に StreamReader を渡してインスタンス
  3. 末尾まで読み込む

まずは一番シンプルな使い方の、一行ずつ読み込んでいくスタイル。

internal class CsvReadExample
{
    // Replace with the directory path where the csv is saved
    private const string Path = @"";

    public void ReadCsv()
    {
        using var reader = new StreamReader($@"{Path}\user.csv");
        using var csv = new CsvReader(reader, CultureInfo.InvariantCulture);
        
        while(csv.Read())
        {
            var user = csv.GetRecord<UserRecord>();
            Console.WriteLine($"Id: {user.Id}, Name: {user.Name}, Email: {user.Email}");
        }
        
    }
}

internal record UserRecord(int Id, string Name, string Email);

複数行をまとめて読み込む

    public void ReadMultiRecords()
    {
        using var reader = new StreamReader($@"{DirectoryPath}\user.csv");
        using var csv = new CsvReader(reader, CultureInfo.InvariantCulture);

        //var users = csv.GetRecords<UserRecord>().ToList();
        var users = csv.GetRecords<UserRecord>();

        foreach(var user in users)
            Console.WriteLine($"Id: {user.Id}, Name: {user.Name}, Email: {user.Email}");
    }

CsvReader に GetRecords ってメソッドが用意されてあるからこれを使う。内部的には一行ずつ読み取ってるから上の例とやってる事は同じだけど、呼び出し側がシンプルになるから良いね。

yield return で一行ずつ末尾まで読み取るような実装になってるから、戻り値を ToList() とかしちゃうとメモリをバカ食いしてしまう。メモリに置きたい時は丁寧に実装する。

また時間があれば続きを書く。Attribute も試してみたい。

CSV ファイルの書き込み

ヘッダーのマッピング

フィールドのバリデーション

BOM 付きにする