概要

CSV (Comma-Separated Values) は、テキストベースのシンプルなファイル形式である。
各行がデータレコードを表しており、フィールドはカンマ (または他の区切り文字) で区切られている。

C#でCSVファイルを読み込む場合は、主にSystem.IO名前空間のクラスを使用する。
File.ReadAllLinesメソッドを使用してファイルの全行を文字列配列として読み込み、次に、String.Splitメソッドで各行をフィールドに分割する。

CSVファイルへ書き込む場合は、StreamWriterクラスを使用して行うことができる。
各フィールドをカンマで連結して、WriteLineメソッドで1行ずつファイルに書き込む。

より複雑なCSV操作には、サードパーティのライブラリを利用することも可能である。
CsvHelperライブラリは人気のあるライブラリであり、読み書きの柔軟性が高く、大規模なCSVファイルの処理に適している。

CSVファイルを扱う時の注意点として、フィールド内にカンマが含まれる場合の処理がある。
このような場合、フィールドを引用符で囲むなどの対策が必要である。

また、文字エンコーディングの問題にも注意が必要であり、特に異なる言語や地域のデータを扱う場合は重要である。

C#のLINQを活用すると、CSVデータの効率的な操作や分析が可能になる。
例えば、特定の条件に合うレコードのフィルタリング、あるいは、データの集計等を簡単に行うことができる。

セキュリティの観点からは、外部ソースからのCSVファイルを扱う場合には注意が必要である。
悪意のあるデータが含まれている可能性があるため、適切な入力検証とサニタイズを行うことが重要である。

パフォーマンスを考慮する場合、大規模なCSVファイルを扱う場合はストリーミング読み取りを検討する。
これにより、メモリ使用量を抑えつつ効率的に処理を行うことができる。


TextFieldParserクラス

CSVファイルを読み込むには、.NET Framework 2.0で追加されたTextFieldParserクラス(Microsoft.VisualBasic.FileIO名前空間)を使用する。
TextFieldParserクラスを使用することで、CSVファイルを読み込み、各行の各フィールドの文字列を簡潔に取得することができる。

TextFieldParserクラスは、Microsoft.VisualBasic.FileIO名前空間に含まれているため、
C#で使用する場合は、一般的に、Microsoft.VisualBasic.dllファイルへの参照を追加する必要がある。

※注意 1
このクラスはVisual Basicのライブラリの一部であるため、C#で使用する場合は追加の依存関係が生じることに注意する。

※注意 2
2024年11月現在、このクラスはLinuxおよび.NET 8 / C# 12でも使用できることを確認している。

RFC 4180との関係

TextFieldParserクラスは多くのRFC 4180の要件を満たしている。 ただし、いくつかの点で完全な準拠ではない。

  • 準拠している点
    • カンマで区切られたフィールドの処理
    • 引用符で囲まれたフィールド内のカンマの処理
    • 改行文字を含むフィールドの処理

  • 部分的に準拠している点
    • 引用符の扱い
      TextFieldParserクラスは引用符の使用を柔軟に設定できるが、デフォルトの動作がRFC 4180と完全に一致するわけではない。

  • 準拠していない可能性がある点
    • ヘッダ行の特別な扱い
      RFC 4180はヘッダ行について言及しているが、TextFieldParserクラスにはヘッダ行を自動的に識別する機能がない。
    • 厳密なCRLF改行の要求
      RFC 4180はCRLF (\r\n) を改行として指定しているが、TextFieldParserクラスはより柔軟である。


以下の例では、TextFieldParserクラスを使用して、RFC 4180の仕様に近づけている。

 using Microsoft.VisualBasic.FileIO;
 
 using (TextFieldParser parser = new TextFieldParser("sample.csv"))
 {
    parser.TextFieldType = FieldType.Delimited;
    parser.SetDelimiters(",");
    parser.HasFieldsEnclosedInQuotes = true;  // RFC 4180に準拠するため
 
    // ヘッダ行の処理
    string[] headers = parser.ReadFields();
 
    while (!parser.EndOfData)
    {
       string[] fields = parser.ReadFields();
       // フィールドの処理を行う
    }
 }


CSVファイルの読み込み

エンコーディングの指定

TextFieldParserクラスを使用してCSVファイルを読み込むには、
まず、TextFieldParserクラスのコンストラクタに処理するCSVファイルを指定して、インスタンスを生成する。

CSVファイルに日本語が含まれている場合は、文字コードを指定する。

 using System.Text;
 using Microsoft.VisualBasic.FileIO;
 
 var parser = new TextFieldParser(@"hoge.csv", Encoding.GetEncoding("Shift_JIS"));  // Shift-JISを指定


デリミタの指定

デリミタを指定する場合は、TextFieldTypeプロパティにFieldType.Delimitedを指定する。

SetDelimitersメソッドを使用して、区切り文字を指定する。(複数の区切り文字が指定可能)
CSVファイルの場合は、,(カンマ)を指定する。

また、FieldType.FixedWidthを指定する場合、フィールドが固定幅のファイルも扱うことができる。

 parser.TextFieldType = FieldType.Delimited;
 parser.SetDelimiters(",");  // 区切り文字はカンマを指定する


レコードの取得

TextFieldParserクラスのReadFieldsメソッドを実行するごとに、CSVファイルを1行ずつ読むことができる。
ReadFieldsメソッドは、読み込んだレコードの全てのフィールドを文字列配列に変換して返す。

次のレコードが存在するかどうかはEndOfDataプロパティにより判定できるため、繰り返し文を使用してCSVファイル全体を処理する。

 while (!parser.EndOfData)
 {
     // 配列rowの要素は読み込んだレコードの各フィールドの値
     string[] row = parser.ReadFields();  // 1レコード読み込む
 }


使用例

以下の例では、sample.csvファイルを読み込み、各フィールドを切り出してタブ区切りで画面に出力する。
改行文字および空白文字がどのように処理されるかを分かりやすくするために、それぞれをnと_に置換して出力している。

  • HasFieldsEnclosedInQuotesプロパティ
    フィールドに改行やデリミタを含める為に引用符を使っているようなフォーマットを考慮する場合は、
    HasFieldsEnclosedInQuotesプロパティをtrueに設定する。(初期値はtrue)

  • TrimWhiteSpaceプロパティ
    フィールドの前後の空白文字を削除しない場合は、TrimWhiteSpaceプロパティをfalseに設定する。(デフォルトは、true)


 // 同期処理
 
 using System;
 using System.Text;
 using Microsoft.VisualBasic.FileIO;
 
 class CSVParser
 {
    static void Main()
    {
       using (var parser = new TextFieldParser("sample.csv", Encoding.GetEncoding("Shift_JIS")))
       {
          parser.TextFieldType = FieldType.Delimited;  // フィールドはデリミタにより区切る(可変)
          parser.Delimiters = new[] {","};             // 区切り文字を指定
          parser.CommentTokens = new[] {"#"};          // #で始まる行をコメントとする
 
          // parser.HasFieldsEnclosedInQuotes = false; // 引用符で括られたフィールドを持つか指定
          // parser.TrimWhiteSpace = false;            // フィールドの前後に含まれる空白を削除するか指定
 
          while (!parser.EndOfData)
          {
             try
             {
                string[] row = parser.ReadFields();    // 1行読み込む
                foreach (string field in row)
                {
                   field = field.Replace("\r\n", "n"); // 改行をnに置換
                   field = field.Replace(" ", "_");    // 空白を_に置換
                   Console.Write(field + "\t");        // タブ区切りで出力
                }
                Console.WriteLine();
             }
             catch(Exception ex)
             {
                Console.WriteLine(ex.Message);
             }
          }
       }
    }
 }


以下の例では、大規模なCSVファイルでもメモリ効率を良くするため、ストリーミング処理を組み合わせて、上記の例を非同期で行っている。
非同期処理は、I/O操作が頻繁に行われる場合、または、ユーザインターフェースの応答性を維持する必要がある場合に特に有効である。

TextFieldParserクラスは同期的に動作するが、CSVファイルの各レコードの処理を別タスクで行うことにより、全体の処理を非同期に保つ。

 // 非同期処理
 // ストリーミング処理
 
 using System;
 using System.Text;
 using System.IO;
 using System.Threading.Tasks;
 using Microsoft.VisualBasic.FileIO;
 
 class CSVParser
 {
    static async Task Main()
    {
       await ParseCSVAsync("sample.csv");
    }
 
    static async Task ParseCSVAsync(string filePath)
    {
       try
       {
          // FileStreamクラスを使用して、ファイルを非同期でストリーミング読み込み
          using (var fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read, bufferSize: 4096, useAsync: true))
          using (var streamReader = new StreamReader(fileStream, Encoding.GetEncoding("Shift_JIS")))
          using (var parser = new TextFieldParser(streamReader))
          {
             parser.TextFieldType = FieldType.Delimited;
             parser.Delimiters = new[] { "," };
             parser.CommentTokens = new[] { "#" };
 
             while (!parser.EndOfData)
             {
                // ファイルを非同期で1行ずつ読み込む
                string line = await streamReader.ReadLineAsync();
                if (line == null) break;
 
                // 行の解析と処理を別タスクで実行
                await Task.Run(() =>
                {
                   try
                   {
                      string[] fields = parser.ReadFields();
                      if (fields != null)
                      {
                         foreach (string field in fields)
                         {
                            string processedField = field.Replace("\r\n", "n").Replace(" ", "_");
                            Console.Write(processedField + "\t");
                         }
 
                         Console.WriteLine();
                      }
                   }
                   catch (Exception ex)
                   {
                      Console.WriteLine($"行の処理中にエラーが発生: {ex.Message}");
                   }
                });
             }
          }
       }
       catch (Exception ex)
       {
          Console.WriteLine($"ファイルの処理中にエラーが発生: {ex.Message}");
       }
    }
 }


CSVファイルの書き込み

使用例: 同期処理

以下の例では、StreamWriterクラスを使用して、CSVファイルへ書き込んでいる。

 using System;
 using System.Text;
 using System.IO;
 using Microsoft.VisualBasic.FileIO;
 
 class CSVParser
 {
    static void Main()
    {
       // 書き込み処理
       WriteCSV("sample.csv");
    }
 
    static void WriteCSV(string outputFile)
    {
       try
       {
          using (StreamWriter writer = new StreamWriter(outputFile, false, Encoding.GetEncoding("Shift_JIS")))
          {
             // ヘッダ行を書き込む
             writer.WriteLine("列1,列2,列3");
 
             // データ行を書き込む
             writer.WriteLine("値1,値2,値3");
             writer.WriteLine("値4,\"値5,カンマを含む\",値6");
             writer.WriteLine("値7,値8,\"値9\n改行を含む\"");
          }
       }
       catch (Exception ex)
       {
          Console.WriteLine("書き込みエラー: " + ex.Message);
       }
    }
 }


使用例: 非同期処理

以下の例では、上記の例を非同期で処理している。

 // 非同期処理
 
 using System;
 using System.Text;
 using System.IO;
 using System.Threading.Tasks;
 
 class CSVParser
 {
    static async Task Main()
    {
       string outputFile = "sample.txt";
       await WriteCSVAsync(outputFile);
    }
 
    static async Task WriteCSVAsync(string outputFile)
    {
       try
       {
          using (StreamWriter writer = new StreamWriter(outputFile, false, Encoding.GetEncoding("Shift_JIS")))
          {
             // ヘッダ行を書き込む
             await writer.WriteLineAsync("列1,列2,列3");
 
             // レコード行を書き込む
             await writer.WriteLineAsync("値1,値2,値3");
             await writer.WriteLineAsync("値4,\"値5,カンマを含む\",値6");
             await writer.WriteLineAsync("値7,値8,\"値9\n改行を含む\"");
          }
       }
       catch (Exception ex)
       {
          Console.WriteLine("書き込みエラー: " + ex.Message);
       }
    }
 }



CsvHelperライブラリ

CsvHelperライブラリとは

CsvHelperライブラリは、C#で使用される強力なCSVファイル操作ライブラリである。
このライブラリは、CSVファイルの読み書きを簡単かつ効率的に行うための多様な機能を提供している。

CsvHelperライブラリの主な特徴として、高速な処理能力が挙げられる。
大量のデータを含むCSVファイルでも、迅速に処理することができる。
また、メモリ効率も優れており、大規模なファイルを扱う場合にも効果を発揮する。

このライブラリはシンプルなAPIを通じて、複雑なCSV操作を簡単に実行することができる。
例えば、オブジェクトのコレクションをCSVファイルに書き込み、あるいは、CSVファイルの内容をオブジェクトのコレクションとして読み込むことが、数行のコードで可能である。

カスタマイズ性も高く、様々なCSV形式に対応できる。
区切り文字の変更、ヘッダの有無の指定、エスケープ文字の設定等、細かな調整が可能である。
さらに、日付や数値のフォーマット指定、null値の扱い等、データの読み書きに関する詳細な制御もサポートしている。

属性 (Attributes) を使用したマッピング機能も便利である。
クラスのプロパティにCSVのカラム名を対応させることにより、オブジェクトとCSVデータの変換を直感的に行うことができる。

また、フルーエントAPIも提供されており、コードの可読性を高めつつ、複雑な設定を行うことができる。
これにより、CSVファイルの構造や読み書きの挙動を詳細に定義できる。

非同期処理もサポートさあれており、大規模なファイルを扱う場合やI/O処理の効率化が必要な場面で、非同期メソッドを活用することができる。

セキュリティ面でも考慮されており、インジェクション攻撃等のリスクに対する保護機能が組み込まれている。

CsvHelperライブラリは広範なプラットフォームをサポートしており、.NET Framework、.NET Core、.NET Standard等、様々な.NET環境で使用できるため、プロジェクトの移植性も高い。

フルーエントAPIとは

フルーエントAPI (Fluent API) は、メソッドチェーンを使用してコードをより読みやすく、より自然な言語に近い形で記述できるプログラミングパターンである。

CsvHelperライブラリのフルーエントAPIは、主に設定やマッピングの定義に使用される。
これにより、複雑な設定も直感的に記述できるようになる。

メソッドチェーンを使用することにより、追加の設定 (例: 日付フォーマット) を簡潔に記述することができる。

  • 可読性の向上
    コードが自然言語に近い形で記述されるため、理解しやすくなる。
  • コードの簡潔さ
    複数の設定を1行で記述できるため、コードが簡潔になる。
  • 柔軟性
    必要な設定のみを追加でき、不要な設定は省略できる。
  • インテリセンスのサポート
    IDEのコード補完機能と相性が良く、利用可能なオプションを容易に把握できる。
  • エラーの検出
    コンパイル時にエラーを検出しやすくなる。


以下の例では、PersonMapクラスを使用してCSVファイルの構造を定義している。
各Mapメソッド呼び出しは、プロパティとCSVカラムのマッピングを表している。

 public class PersonMap : ClassMap<Person>
 {
    public PersonMap()
    {
       Map(m => m.Id).Name("社員番号");
       Map(m => m.Name).Name("氏名");
       Map(m => m.BirthDate).Name("生年月日")
                            .TypeConverterOption.Format("yyyy年MM月dd日");
    }
 }
 
 // 使用例
 var config = new CsvConfiguration(CultureInfo.InvariantCulture)
 {
    Encoding = Encoding.GetEncoding("shift_jis"),
    HasHeaderRecord = true
 };
 
 using (var writer = new StreamWriter("output.csv"))
 using (var csv = new CsvWriter(writer, config))
 {
    csv.Context.RegisterClassMap<PersonMap>();
    csv.WriteRecords(records);
 }


CsvHelperライブラリのインストール

RiderまたはVisual StudioからNuGetを使用して、CsvHelperライブラリをインストールする。

  • Riderの場合
    1. プロジェクトを開く。
    2. [ツール]メインメニュー - [Nuget] - [ソリューション の Nuget パッケージを管理] (または、[<プロジェクト名> の Nuget パッケージを管理])を選択する。
    3. メイン画面下部にある[パッケージ]タブから CsvHelper と入力して検索する。
    4. メイン画面下部の右にある[+]ボタンを押下して、CsvHelperライブラリをインストールする。

  • Visual Studioの場合
    1. プロジェクトを開く。
    2. NuGetパッケージマネージャーを開く。
      • [ツール]メインメニュー - [NuGetパッケージマネージャー]を選択して、[ソリューションのNuGetパッケージの管理]を選択する。
      • または、ソリューションエクスプローラーでプロジェクトを右クリックして、コンテキストメニューから[NuGetパッケージの管理]を選択する。
    3. CsvHelperライブラリを検索する。
      NuGetパッケージマネージャーの検索ボックスに CsvHelper と入力して検索する。
    4. CsvHelperライブラリのインストール
      検索結果からCsvHelperライブラリを選択して、[インストール]ボタンを押下する。
    5. インストールの確認ダイアログが表示されるので、[OK]ボタンを押下してインストールを完了する。
    6. 参照の確認
      インストールが完了した後、プロジェクトの参照にCsvHelperライブラリが追加されていることを確認する。

  • パッケージマネージャーコンソールからインストールする場合
    1. プロジェクトを開く。
    2. [表示]メインメニュー - [その他のウィンドウ] - [パッケージマネージャーコンソール]を選択して、パッケージマネージャーコンソールを開く。
    3. パッケージマネージャーコンソールから、CsvHelperライブラリをダウンロードしてインストールする。
      Install-Package CsvHelper
    4. ソリューションエクスプローラーのプロジェクトの参照において、CsvHelperライブラリが追加されていることを確認する。

  • dotnetコマンドを使用する場合
    1. ターミナルを開く。
    2. プロジェクトのルートディレクトリに移動する。
    3. CsvHelperライブラリをインストールする。
      最新の安定版をインストールする場合
      dotnet add package CsvHelper

      バージョンを指定してインストールする場合
      dotnet add package CsvHelper --version <バージョン>

    ※注意
    プロジェクトがGit等のバージョン管理システムを使用している場合、これらの変更がトラッキングされることを確認すること。
    プロジェクトを再ビルドして、新しく追加されたパッケージが正しく統合されていることを確認することを推奨する。


プロジェクトにおいて、CsvHelperライブラリを使用する場合は、ソースコードファイルの先頭にusingステートメントを追加する。

 using CsvHelper;
 using CsvHelper.Configuration;


CsvHelperはライブラリ非常に柔軟であり、様々なカスタマイズが可能である。
例えば、カスタムマッピングを定義して、CSVファイルの列名とクラスのプロパティ名が異なる場合に対応することができる。

また、大規模なCSVファイルを扱う場合は、ストリーミング処理を使用することを推奨する。

CSVファイルの読み込み

同期処理

以下の例では、CsvHelperライブラリを使用して、CSVファイルを同期的に読み込んでいる。

  1. StreamReaderクラスを使用して、CSVファイルを開く。
  2. CsvReaderクラスのインスタンスを生成して、StreamReaderクラスのインスタンスとカルチャ情報を渡す。
  3. GetRecords<オブジェクト名>メソッドを使用して、CSVファイルの各レコードを任意のオブジェクトにマッピングする。


また、異なる日付形式やカルチャ設定を指定することもできる。

// CSVファイルの内容

Id,Name,BirthDate
1,John Doe,1980-01-01
2,Jane Smith,1985-05-15
3,Bob Johnson,1990-12-31


 using System;
 using System.IO;
 using System.Globalization;
 using CsvHelper;
 using CsvHelper.Configuration;
 
 public class Person
 {
    public int Id { get; set; }
    public string Name { get; set; }
    public DateTime BirthDate { get; set; }
 }
 
 class Program
 {
    static void Main(string[] args)
    {
       string filePath = "people.csv";
 
       try
       {
          using (var reader = new StreamReader(filePath))
          using (var csv = new CsvReader(reader, CultureInfo.InvariantCulture))
          {
             var records = csv.GetRecords<Person>();
 
             foreach (var person in records)
             {
                Console.WriteLine($"Id: {person.Id}, Name: {person.Name}, Birth Date: {person.BirthDate:d}");
             }
          }
       }
       catch (Exception ex)
       {
          Console.WriteLine($"予期せぬエラーが発生: {ex.Message}");
       }
    }
 }


非同期処理

以下の例では、CsvHelperライブラリを使用して、CSVファイルをストリーミング処理かつ非同期に読み込んでいる。
ファイル全体をメモリに読み込む代わりに、1レコードずつ処理するため、大規模なCSVファイルでもメモリ使用量を抑えられる。
ファイルの読み込みと各レコードの処理が非同期で行われるため、アプリケーションの応答性が向上する。

また、キャンセレーショントークンを追加しているため、長時間の処理をキャンセルすることも可能である。

await foreach句を使用しているため、.NET Core 3.0以降または.NET 5.0以降で動作することに注意する。

 using System;
 using System.IO;
 using System.Globalization;
 using System.Threading;
 using System.Threading.Tasks;
 using CsvHelper;
 using CsvHelper.Configuration;
 
 public class Person
 {
    public int Id { get; set; }
    public string Name { get; set; }
    public DateTime BirthDate { get; set; }
 }
 
 class Program
 {
    static async Task Main(string[] args)
    {
       string filePath = "sample.csv";
       using var cts = new CancellationTokenSource();  // キャンセレーショントークンソースの生成
 
       // キャンセレーショントークンのためのタスクを開始
       var cancellationTask = Task.Run(() =>
       {
          Console.WriteLine("'c'キーを押下して処理をキャンセル");
          if (Console.ReadKey().KeyChar == 'c')
          {
             cts.Cancel();
             Console.WriteLine("キャンセル処理をリクエスト...");
          }
       });
 
       try
       {
          await ProcessCsvFileAsync(filePath, cts.Token);
       }
       catch (OperationCanceledException)
       {
          Console.WriteLine("読み込み処理がキャンセルされました");
       }
       catch (Exception ex)
       {
          Console.WriteLine($"予期せぬエラーが発生: {ex.Message}");
       }
 
       // キャンセレーションタスクが完了するまで待機
       await cancellationTask;
    }
 
    static async Task ProcessCsvFileAsync(string filePath, CancellationToken cancellationToken)
    {
       var configuration = new CsvConfiguration(CultureInfo.InvariantCulture)
       {
          HasHeaderRecord = true,
          MissingFieldFound = null
       };
 
       using var reader = new StreamReader(filePath);
       using var csv = new CsvReader(reader, configuration);
 
       var records = csv.GetRecordsAsync<Person>(cancellationToken);
 
       await foreach (var person in records.WithCancellation(cancellationToken))
       {
          await ProcessPersonAsync(person, cancellationToken);
       }
    }
 
    static async Task ProcessPersonAsync(Person person, CancellationToken cancellationToken)
    {
       // キャンセレーショントークンの確認
       cancellationToken.ThrowIfCancellationRequested();
 
       // ここで各Personオブジェクトを非同期に処理
       // 例: データベースへの保存、外部APIの呼び出し等
       await Task.Delay(1000, cancellationToken);
 
       Console.WriteLine($"Processed: Id: {person.Id}, Name: {person.Name}, Birth Date: {person.BirthDate:d}");
    }
 }


CSVファイルの書き込み

同期処理

以下の例では、CsvHelperライブラリを使用して、CSVファイルへ同期的に作成している。

System.Text.Encoding.CodePagesを使用しているため、NET Core 3.0以降または.NET 5.0以降が必要となる。
もし、古いバージョンの.NETを使用している場合は、System.Text.Encoding.CodePagesをNuGetからインストールする必要がある。

 using System;
 using System.IO;
 using System.Text;
 using System.Globalization;
 using System.Collections.Generic;
 using CsvHelper;
 using CsvHelper.Configuration;
 using CsvHelper.Configuration.Attributes;
 
 public class Person
 {
    [Name("社員番号")]
    public int Id { get; set; }
 
    [Name("氏名")]
    public string Name { get; set; }
 
    [Name("生年月日")]
    [Format("yyyy年MM月dd日")]
    public DateTime BirthDate { get; set; }
 }
 
 class Program
 {
    static void Main(string[] args)
    {
       string filePath = "sample.csv";
       Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
       Encoding shiftJis = Encoding.GetEncoding("shift_jis");
 
       var records = new List<Person>
       {
          new Person { Id = 1, Name = "山田 太郎", BirthDate = new DateTime(1980, 1, 1) },
          new Person { Id = 2, Name = "佐藤 花子", BirthDate = new DateTime(1985, 5, 15) },
          new Person { Id = 3, Name = "鈴木 一郎", BirthDate = new DateTime(1990, 12, 31) }
       };
 
        var config = new CsvConfiguration(CultureInfo.InvariantCulture)
        {
           Encoding        = shiftJis,  // Shift-JISエンコーディングを設定
           HasHeaderRecord = true       // CSVファイルにヘッダを書き込む
        };
 
        using (var writer = new StreamWriter(filePath, false, encoding))
        using (var csv = new CsvWriter(writer, config))
        {
           // DateTimeコンバータを追加して、日付を日本語形式で出力
           csv.Context.TypeConverterCache.AddConverter<DateTime>(new CustomDateTimeConverter());
           csv.WriteRecords(records);
        }
    }
 }
 
 // DateTimeConverterを拡張して、日付を"yyyy年MM月dd日"形式で出力するカスタムコンバータを定義
 public class CustomDateTimeConverter : CsvHelper.TypeConversion.DateTimeConverter
 {
    public override string ConvertToString(object value, IWriterRow row, MemberMapData memberMapData)
    {
       if (value is DateTime dateTime)
       {
          return dateTime.ToString("yyyy年MM月dd日");
       }
 
       return base.ConvertToString(value, row, memberMapData);
    }
 }


// 生成されるCSVファイル

社員番号,氏名,生年月日
1,山田 太郎,1980年01月01日
2,佐藤 花子,1985年05月15日
3,鈴木 一郎,1990年12月31日


非同期処理

以下の例では、CsvHelperライブラリを使用して、CSVファイルを非同期的に作成している。

 using System;
 using System.IO;
 using System.Text;
 using System.Globalization;
 using System.Collections.Generic;
 using System.Threading.Tasks;
 using CsvHelper;
 using CsvHelper.Configuration;
 using CsvHelper.Configuration.Attributes;
 
 public class Person
 {
    [Name("社員番号")]
    public int Id { get; set; }
 
    [Name("氏名")]
    public string Name { get; set; }
 
    [Name("生年月日")]
    [Format("yyyy年MM月dd日")]
    public DateTime BirthDate { get; set; }
 }
 
 class Program
 {
    static async Task Main(string[] args)
    {
       string filePath   = "sample.csv";
       Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
       Encoding shiftJis = Encoding.GetEncoding("shift_jis");  // 文字コードをShift-JISに指定する場合
 
       var records = new List<Person>
       {
          new Person { Id = 1, Name = "山田 太郎", BirthDate = new DateTime(1980, 1, 1) },
          new Person { Id = 2, Name = "佐藤 花子", BirthDate = new DateTime(1985, 5, 15) },
          new Person { Id = 3, Name = "鈴木 一郎", BirthDate = new DateTime(1990, 12, 31) }
       };
 
       var config = new CsvConfiguration(CultureInfo.InvariantCulture)
       {
          Encoding = shiftJis,    // Shift-JISエンコーディングを設定
          HasHeaderRecord = true  // CSVファイルにヘッダを書き込む
       };
 
       await using (var writer = new StreamWriter(filePath, false, shiftJis))
       await using (var csv = new CsvWriter(writer, config))
       {
          // DateTimeコンバータを追加して、日付を日本語形式で出力
          csv.Context.TypeConverterCache.AddConverter<DateTime>(new CustomDateTimeConverter());
          await csv.WriteRecordsAsync(records);
       }
    }
 }
 
 // 日付を"yyyy年MM月dd日"形式で出力するカスタムコンバータを定義
 public class CustomDateTimeConverter : CsvHelper.TypeConversion.DateTimeConverter
 {
    public override string ConvertToString(object value, IWriterRow row, MemberMapData memberMapData)
    {
       if (value is DateTime dateTime)
       {
          return dateTime.ToString("yyyy年MM月dd日");
       }
 
       return base.ConvertToString(value, row, memberMapData);
    }
 }


// 生成されるCSVファイル

社員番号,氏名,生年月日
1,山田 太郎,1980年01月01日
2,佐藤 花子,1985年05月15日
3,鈴木 一郎,1990年12月31日