【C#】CsvHelper 使用マニュアル

【C#】CsvHelper 使用マニュアル

CsvHelper は CSV ファイルの読み書きのための .NET ライブラリです。非常に高速で、柔軟かつ使いやすいです。

最終更新 2024/01/19 22:26
丹枫无迹
読了目安 7 分
カテゴリ
.NET
タグ
.NET C# CSV

本文のコードは CsvHelper 15.0.5 に基づいています

はじめに

CsvHelper は CSV ファイルを読み書きするための .NET ライブラリです。非常に高速で柔軟性が高く、使いやすいです。

CsvHelper は .NET Standard 2.0 上に構築されており、ほぼどこでも実行できます。

GitHub アドレス: https://github.com/joshclose/csvhelper

モジュール

モジュール 機能
CsvHelper CSV データを読み書きするコアクラス。
CsvHelper.Configuration CsvHelper の読み書き動作を設定するクラス。
CsvHelper.Configuration.Attributes CsvHelper の属性を設定するクラス。
CsvHelper.Expressions LINQ 式を生成するクラス。
CsvHelper.TypeConversion CSV フィールドと .NET 型を相互変換するクラス。

読み取り

テストクラス

public class Foo
{
    public int ID { get; set; }

    public string Name { get; set; }
}

csv ファイルのデータ

ID,Name
1,Tom
2,Jerry

すべてのレコードを読み取る

using (var reader = new StreamReader("foo.csv"))
{
    using (var csv = new CsvReader(reader, CultureInfo.InvariantCulture))
    {
        var records = csv.GetRecords<Foo>();
    }
}

csv ファイルの読み取り時、空行は無視されますが、空行にスペースが含まれているとエラーになります。 Excel で編集された CSV ファイルの場合、空行は区切り文字 , のみの行になり、同様にエラーになります。

1行ずつ読み取る

using (var reader = new StreamReader("foo.csv"))
{
    using (var csv = new CsvReader(reader, CultureInfo.InvariantCulture))
    {
        while (csv.Read())
        {
            var record = csv.GetRecord<Foo>();
        }
    }
}

GetRecords<T> メソッドは yield を使用して IEnumerable<T> を返し、ToList または ToArray を呼び出さない限りすべての内容を一度にメモリに読み込むことはありません。そのため、このような1行ずつ読み取る書き方はあまり必要ありません。

単一フィールドの読み取り

using (var csv = new CsvReader(reader, CultureInfo.InvariantCulture))
{
    csv.Read();
    csv.ReadHeader();

    while (csv.Read())
    {
        var id = csv.GetField<int>(0);
        var name = csv.GetField<string>("Name");
    }
}

行ごとに読み取る場合、ヘッダー行を無視することもできますが、ここではできません。

csv.Read(); はヘッダーを読み取るためのものです。これがないと、while ループの最初でヘッダーを取得してしまい、エラーになります。

csv.ReadHeader(); はヘッダーを設定するためのものです。これがないと、csv.GetField<string>("Name") でヘッダーが見つからずエラーになります。

TryGetField を使用すると、予期しないエラーを防げます。

csv.TryGetField(0, out int id);

書き込み

すべてのレコードを書き込む

var records = new List<Foo>
{
    new Foo { ID = 1, Name = "Tom" },
    new Foo { ID = 2, Name = "Jerry" },
};

using (var writer = new StreamWriter("foo.csv"))
{
    using (var csv = new CsvWriter(writer, CultureInfo.InvariantCulture))
    {
        csv.WriteRecords(records);
    }
}

1行ずつ書き込む

using (var writer = new StreamWriter("foo.csv"))
{
    using (var csv = new CsvWriter(writer, CultureInfo.InvariantCulture))
    {
        foreach (var record in records)
        {
            csv.WriteRecord(record);
        }
    }
}

フィールド単位で書き込む

using (var writer = new StreamWriter("foo.csv"))
{
    using (var csv = new CsvWriter(writer, CultureInfo.InvariantCulture))
    {
        csv.WriteHeader<Foo>();
        csv.NextRecord();

        foreach (var record in records)
        {
            csv.WriteField(record.ID);
            csv.WriteField(record.Name);
            csv.NextRecord();
        }
    }
}

属性

Index

Index 属性はフィールドの順序を指定します。

ファイルを読み取る際、ヘッダーがない場合は順序でしかフィールドを特定できません。

public class Foo
{
    [Index(0)]
    public int ID { get; set; }

    [Index(1)]
    public string Name { get; set; }
}

using (var reader = new StreamReader("foo.csv"))
{
    using (var csv = new CsvReader(reader, CultureInfo.InvariantCulture))
    {
        csv.Configuration.HasHeaderRecord = false;

        var records = csv.GetRecords<Foo>().ToList();
    }
}

csv.Configuration.HasHeaderRecord = falseCsvReader にヘッダーがないことを伝えます。この行は必須で、追加しないとデフォルトで1行目をヘッダーとしてスキップし、結果が1行少なくなります。データ量が多いとこのバグを見つけるのが難しくなります。

ファイルに書き込む際も Index の順序で書き込まれます。ヘッダーを書き込みたくない場合も csv.Configuration.HasHeaderRecord = false; を追加する必要があります。

Name

フィールド名と列名が異なる場合、Name 属性を使用できます。

public class Foo
{
    [Name("id")]
    public int ID { get; set; }

    [Name("name")]
    public string Name { get; set; }
}

NameIndex

NameIndex は CSV ファイル内の同名列を処理するために使用します。

public class Foo
{
    ...

    [Name("Name")]
    [NameIndex(0)]
    public string FirstName { get; set; }

    [Name("Name")]
    [NameIndex(1)]
    public string LastName { get; set; }
}

Ignore

フィールドを無視します。

Optional

読み取り時に一致するフィールドが見つからない場合、無視します。

public class Foo
{
    ...

    [Optional]
    public string Remarks { get; set; }
}

Default

読み取るフィールドが空の場合、Default 属性でデフォルト値を指定できます。

Default 属性は読み取り時のみ有効で、書き込み時に空の値をデフォルト値に置き換えて書き込むことはありません。

NullValues

public class Foo
{
    ...

    [NullValues("None", "none", "Null", "null")]
    public string None { get; set; }
}

ファイルを読み取る際、CSV ファイル内の特定のフィールドの値が空の場合、読み取り後の値は "" であり null ではありません。NullValues 属性を指定すると、CSV ファイル内の特定のフィールドの値が NullValues で指定された値の場合、読み取り後に null になります。

Default 属性も同時に指定されている場合、この属性は無効になります。

厄介なのは、ファイル書き込み時にはこの属性が機能しないことです。そのため、読み取りと書き込みの一貫性が失われます。

Constant

Constant 属性はフィールドに定数を指定します。読み取り時も書き込み時もこの値が使用され、他のマッピングや設定は無視されます。

Format

Format は型変換時に使用する文字列形式を指定します。

たとえば、数値や日付型の場合に形式を指定することがよくあります。

public class Foo
{
    ...

    [Format("0.00")]
    public decimal Amount { get; set; }

    [Format("yyyy-MM-dd HH:mm:ss")]
    public DateTime JoinTime { get; set; }
}

BooleanTrueValues と BooleanFalseValues

これらの2つの属性は、bool 型を指定した形式に変換して表示するために使用します。

public class Foo
{
    ...

    [BooleanTrueValues("yes")]
    [BooleanFalseValues("no")]
    public bool Vip { get; set; }
}

NumberStyles

public class Foo
{
    ...

    [Format("X2")]
    [NumberStyles(NumberStyles.HexNumber)]
    public int Data { get; set; }
}

特に便利なのは NumberStyles.HexNumberNumberStyles.AllowHexSpecifier です。これら2つの列挙体の動作はほぼ同じです。この属性は読み取り時のみ有効で、書き込み時に16進数に変換して書き込むことはありません。そのため読み取りと書き込みの一貫性が失われるため、Format 属性で書き込み形式を指定してください。

マッピング

マッピング対象のクラスに属性を付与できない場合、ClassMap を使用したマッピングが可能です。

マッピングを使用しても属性を使用しても効果は同じで、困った点も同じです。以下の例では、プロパティを使用して上記の属性の機能を実現しています。

public class Foo2
{
    public int ID { get; set; }

    public string Name { get; set; }

    public decimal Amount { get; set; }

    public DateTime JoinTime { get; set; }

    public string Msg { get; set; }

    public string Msg2 { get; set; }

    public bool Vip { get; set; }

    public string Remarks { get; set; }

    public string None { get; set; }

    public int Data { get; set; }
}

public class Foo2Map : ClassMap<Foo2>
{
    public Foo2Map()
    {
        Map(m => m.ID).Index(0).Name("id");
        Map(m => m.Name).Index(1).Name("name");
        Map(m => m.Amount).TypeConverterOption.Format("0.00");
        Map(m => m.JoinTime).TypeConverterOption.Format("yyyy-MM-dd HH:mm:ss");
        Map(m => m.Msg).Default("Hello");
        Map(m => m.Msg2).Ignore();
        Map(m => m.Vip)
            .TypeConverterOption.BooleanValues(true, true, new string[] { "yes" })
            .TypeConverterOption.BooleanValues(false, true, new string[] { "no" });
        Map(m => m.Remarks).Optional();
        Map(m => m.None).TypeConverterOption.NullValues("None", "none", "Null", "null");
        Map(m => m.Data)
            .TypeConverterOption.NumberStyles(NumberStyles.HexNumber)
            .TypeConverterOption.Format("X2");
    }
}

マッピングを使用する前に、登録が必要です。

csv.Configuration.RegisterClassMap<Foo2Map>();

ConvertUsing

ConvertUsing を使用すると、デリゲートメソッドで型変換を実装できます。

// 定数
Map(m => m.Constant).ConvertUsing(row => 3);

// 2つの列を結合する
Map(m => m.Name).ConvertUsing(row => $"{row.GetField<string>("FirstName")} {row.GetField<string>("LastName")}");

Map(m => m.Names).ConvertUsing(row => new List<string> { row.GetField<string>("Name") } );

設定

Delimiter

区切り文字

csv.Configuration.Delimiter = ",";

HasHeaderRecord

この設定は前述しました。最初の行をヘッダーとするかどうかです。

csv.Configuration.HasHeaderRecord = false;

IgnoreBlankLines

空行を無視するかどうか。デフォルトは true です。

csv.Configuration.IgnoreBlankLines = false;

スペースのみまたは , のみの行は無視できません。

AllowComments

コメントを許可するかどうか。コメントは # で始まります。

csv.Configuration.AllowComments = true;

Comment

コメントアウトされた行を示す文字を取得または設定します。デフォルトは # です。

csv.Configuration.Comment = '/';

BadDataFound

不正なデータが見つかったときにトリガーされる関数を設定します。ログ記録などに使用できます。

IgnoreQuotes

解析時に引用符を無視し、他の文字と同様に扱うかどうかを取得または設定します。

デフォルトは false です。文字列内に引用符がある場合、3つの " が連続している場合のみ、読み取った文字列に1つの " が含まれます。1つの場合は無視され、2つの場合はエラーになります。

true の場合、" はそのまま文字列として返されます。

csv.Configuration.IgnoreQuotes = true;

CsvWriter にはこのプロパティはありません。文字列に " が含まれていると、3つの " が連続して出力されます。

TrimOptions

フィールドの前後の空白を削除します。

csv.Configuration.TrimOptions = TrimOptions.Trim;

PrepareHeaderForMatch

PrepareHeaderForMatch は、プロパティ名とヘッダーを照合するための関数を定義します。ヘッダーとプロパティ名の両方がこの関数を通過します。この機能は、ヘッダーからスペースを削除したり、ヘッダーとプロパティ名の大文字小文字が一致しない場合に統一してから比較するために使用できます。

csv.Configuration.PrepareHeaderForMatch = (string header, int index) => header.ToLower();
さらに探索

関連読書

その他の記事
同じカテゴリ / 同じタグ 2026/04/22

各OSバージョンの.NETサポート状況(250707更新)

仮想マシンとテストマシンを使用して、各OSバージョンの.NETサポート状況を確認します。OSインストール後、対応するランタイムをインストールし、Stardustエージェントを実行できることを確認します(合格条件)。

続きを読む
同じカテゴリ / 同じタグ 2026/02/07

AOTの使用経験のまとめ

プロジェクト作成当初から、新機能を追加したり新しい構文を使用したりした場合には、すぐにAOT公開テストを実施するという良い習慣を身につけるべきです。

続きを読む