(同じ利用者による、間の10版が非表示)
34行目: 34行目:
C#で使用する場合は、一般的に、Microsoft.VisualBasic.dllファイルへの参照を追加する必要がある。<br>
C#で使用する場合は、一般的に、Microsoft.VisualBasic.dllファイルへの参照を追加する必要がある。<br>
<br>
<br>
<u>※注意</u><br>
<u>※注意 1</u><br>
<u>このクラスはVisual Basicのライブラリの一部であるため、C#で使用する場合は追加の依存関係が生じることに注意する。</u><br>
<u>このクラスはVisual Basicのライブラリの一部であるため、C#で使用する場合は追加の依存関係が生じることに注意する。</u><br>
<br>
<u>※注意 2</u><br>
<u>2024年11月現在、このクラスはLinuxおよび.NET 8 / C# 12でも使用できることを確認している。</u><br>
<br>
<br>
==== RFC 4180との関係 ====
==== RFC 4180との関係 ====
128行目: 131行目:
<br>
<br>
  <syntaxhighlight lang="c#">
  <syntaxhighlight lang="c#">
// 同期処理
  using System;
  using System;
  using System.Text;
  using System.Text;
168行目: 173行目:
  </syntaxhighlight>
  </syntaxhighlight>
<br>
<br>
==== CSVファイルの書き込み ====
以下の例では、大規模なCSVファイルでもメモリ効率を良くするため、ストリーミング処理を組み合わせて、上記の例を非同期で行っている。<br>
===== 使用例 =====
非同期処理は、I/O操作が頻繁に行われる場合、または、ユーザインターフェースの応答性を維持する必要がある場合に特に有効である。<br>
以下の例では、StreamWriterクラスを使用して、CSVファイルへ書き込んでいる。<br>
<br>
TextFieldParserクラスは同期的に動作するが、CSVファイルの各レコードの処理を別タスクで行うことにより、全体の処理を非同期に保つ。<br>
<br>
<br>
  <syntaxhighlight lang="c#">
  <syntaxhighlight lang="c#">
// 非同期処理
// ストリーミング処理
  using System;
  using System;
  using System.Text;
  using System.Text;
  using System.IO;
  using System.IO;
using System.Threading.Tasks;
  using Microsoft.VisualBasic.FileIO;
  using Microsoft.VisualBasic.FileIO;
   
   
  class CSVParser
  class CSVParser
  {
  {
     static void Main()
     static async Task Main()
     {
     {
       // 読み込み処理
       await ParseCSVAsync("sample.csv");
      ReadCSV("input.csv");
      // 書き込み処理
      WriteCSV("output.csv");
     }
     }
   
   
     static void ReadCSV(string inputFile)
     static async Task ParseCSVAsync(string filePath)
     {
     {
       using (var parser = new TextFieldParser(inputFile, Encoding.GetEncoding("Shift_JIS")))
       try
       {
       {
           parser.TextFieldType = FieldType.Delimited;
           // FileStreamクラスを使用して、ファイルを非同期でストリーミング読み込み
          parser.Delimiters = new[] {","};
          using (var fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read, bufferSize: 4096, useAsync: true))
          parser.CommentTokens = new[] {"#"};
          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)
            while (!parser.EndOfData)
          {
            try
             {
             {
                 string[] row = parser.ReadFields();
                // ファイルを非同期で1行ずつ読み込む
                 foreach (string field in row)
                 string line = await streamReader.ReadLineAsync();
                if (line == null) break;
                // 行の解析と処理を別タスクで実行
                 await Task.Run(() =>
                 {
                 {
                   string processedField = field.Replace("\r\n", "n").Replace(" ", "_");
                   try
                  Console.Write(processedField + "\t");
                  {
                }
                      string[] fields = parser.ReadFields();
            }
                      if (fields != null)
            catch(Exception ex)
                      {
            {
                        foreach (string field in fields)
                Console.WriteLine("読み込みエラー: " + ex.Message);
                        {
                            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}");
      }
    }
}
</syntaxhighlight>
<br>
==== CSVファイルの書き込み ====
===== 使用例: 同期処理 =====
以下の例では、StreamWriterクラスを使用して、CSVファイルへ書き込んでいる。<br>
<br>
<syntaxhighlight lang="c#">
using System;
using System.Text;
using System.IO;
using Microsoft.VisualBasic.FileIO;
class CSVParser
{
    static void Main()
    {
      // 書き込み処理
      WriteCSV("sample.csv");
     }
     }
   
   
238行目: 288行目:
  }
  }
  </syntaxhighlight>
  </syntaxhighlight>
<br>
===== 使用例: 非同期処理 =====
以下の例では、上記の例を非同期で処理している。<br>
<br>
<syntaxhighlight lang="c#">
// 非同期処理
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);
      }
    }
}
</syntaxhighlight>
<br><br>
== CsvHelperライブラリ ==
==== CsvHelperライブラリとは ====
CsvHelperライブラリは、C#で使用される強力なCSVファイル操作ライブラリである。<br>
このライブラリは、CSVファイルの読み書きを簡単かつ効率的に行うための多様な機能を提供している。<br>
<br>
CsvHelperライブラリの主な特徴として、高速な処理能力が挙げられる。<br>
大量のデータを含むCSVファイルでも、迅速に処理することができる。<br>
また、メモリ効率も優れており、大規模なファイルを扱う場合にも効果を発揮する。<br>
<br>
このライブラリはシンプルなAPIを通じて、複雑なCSV操作を簡単に実行することができる。<br>
例えば、オブジェクトのコレクションをCSVファイルに書き込み、あるいは、CSVファイルの内容をオブジェクトのコレクションとして読み込むことが、数行のコードで可能である。<br>
<br>
カスタマイズ性も高く、様々なCSV形式に対応できる。<br>
区切り文字の変更、ヘッダの有無の指定、エスケープ文字の設定等、細かな調整が可能である。<br>
さらに、日付や数値のフォーマット指定、null値の扱い等、データの読み書きに関する詳細な制御もサポートしている。<br>
<br>
属性 (Attributes) を使用したマッピング機能も便利である。<br>
クラスのプロパティにCSVのカラム名を対応させることにより、オブジェクトとCSVデータの変換を直感的に行うことができる。<br>
<br>
また、フルーエントAPIも提供されており、コードの可読性を高めつつ、複雑な設定を行うことができる。<br>
これにより、CSVファイルの構造や読み書きの挙動を詳細に定義できる。<br>
<br>
非同期処理もサポートさあれており、大規模なファイルを扱う場合やI/O処理の効率化が必要な場面で、非同期メソッドを活用することができる。<br>
<br>
セキュリティ面でも考慮されており、インジェクション攻撃等のリスクに対する保護機能が組み込まれている。<br>
<br>
CsvHelperライブラリは広範なプラットフォームをサポートしており、.NET Framework、.NET Core、.NET Standard等、様々な.NET環境で使用できるため、プロジェクトの移植性も高い。<br>
<br>
==== フルーエントAPIとは ====
フルーエントAPI (Fluent API) は、メソッドチェーンを使用してコードをより読みやすく、より自然な言語に近い形で記述できるプログラミングパターンである。<br>
<br>
CsvHelperライブラリのフルーエントAPIは、主に設定やマッピングの定義に使用される。<br>
これにより、複雑な設定も直感的に記述できるようになる。<br>
<br>
メソッドチェーンを使用することにより、追加の設定 (例: 日付フォーマット) を簡潔に記述することができる。<br>
* 可読性の向上
*: コードが自然言語に近い形で記述されるため、理解しやすくなる。
* コードの簡潔さ
*: 複数の設定を1行で記述できるため、コードが簡潔になる。
* 柔軟性
*: 必要な設定のみを追加でき、不要な設定は省略できる。
* インテリセンスのサポート
*: IDEのコード補完機能と相性が良く、利用可能なオプションを容易に把握できる。
* エラーの検出
*: コンパイル時にエラーを検出しやすくなる。
<br>
以下の例では、PersonMapクラスを使用してCSVファイルの構造を定義している。<br>
各Mapメソッド呼び出しは、プロパティとCSVカラムのマッピングを表している。<br>
<br>
<syntaxhighlight lang="c#">
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);
}
</syntaxhighlight>
<br>
==== CsvHelperライブラリのインストール ====
RiderまたはVisual StudioからNuGetを使用して、CsvHelperライブラリをインストールする。<br>
* Riderの場合
*# プロジェクトを開く。
*# [ツール]メインメニュー - [Nuget] - [ソリューション の Nuget パッケージを管理] (または、[<プロジェクト名> の Nuget パッケージを管理])を選択する。
*# メイン画面下部にある[パッケージ]タブから <u>CsvHelper</u> と入力して検索する。
*# メイン画面下部の右にある[+]ボタンを押下して、CsvHelperライブラリをインストールする。
*: <br>
* Visual Studioの場合
*# プロジェクトを開く。
*# NuGetパッケージマネージャーを開く。
*#* [ツール]メインメニュー - [NuGetパッケージマネージャー]を選択して、[ソリューションのNuGetパッケージの管理]を選択する。
*#* または、ソリューションエクスプローラーでプロジェクトを右クリックして、コンテキストメニューから[NuGetパッケージの管理]を選択する。
*# CsvHelperライブラリを検索する。
*#: NuGetパッケージマネージャーの検索ボックスに <u>CsvHelper</u> と入力して検索する。
*# CsvHelperライブラリのインストール
*#: 検索結果からCsvHelperライブラリを選択して、[インストール]ボタンを押下する。
*# インストールの確認ダイアログが表示されるので、[OK]ボタンを押下してインストールを完了する。
*# 参照の確認
*#: インストールが完了した後、プロジェクトの参照にCsvHelperライブラリが追加されていることを確認する。
*: <br>
* パッケージマネージャーコンソールからインストールする場合
*# プロジェクトを開く。
*# [表示]メインメニュー - [その他のウィンドウ] - [パッケージマネージャーコンソール]を選択して、パッケージマネージャーコンソールを開く。
*# パッケージマネージャーコンソールから、CsvHelperライブラリをダウンロードしてインストールする。
*#: <code>Install-Package CsvHelper</code>
*# ソリューションエクスプローラーのプロジェクトの参照において、CsvHelperライブラリが追加されていることを確認する。
*: <br>
* <code>dotnet</code>コマンドを使用する場合
*# ターミナルを開く。
*# プロジェクトのルートディレクトリに移動する。
*# CsvHelperライブラリをインストールする。
*#: 最新の安定版をインストールする場合
*#: <code>dotnet add package CsvHelper</code>
*#: <br>
*#: バージョンを指定してインストールする場合
*#: <code>dotnet add package CsvHelper --version <バージョン></code>
*#: <br>
*: <u>※注意</u>
*: <u>プロジェクトがGit等のバージョン管理システムを使用している場合、これらの変更がトラッキングされることを確認すること。</u>
*: <u>プロジェクトを再ビルドして、新しく追加されたパッケージが正しく統合されていることを確認することを推奨する。</u>
<br>
プロジェクトにおいて、CsvHelperライブラリを使用する場合は、ソースコードファイルの先頭にusingステートメントを追加する。<br>
<syntaxhighlight lang="c#">
using CsvHelper;
using CsvHelper.Configuration;
</syntaxhighlight>
<br>
CsvHelperはライブラリ非常に柔軟であり、様々なカスタマイズが可能である。<br>
例えば、カスタムマッピングを定義して、CSVファイルの列名とクラスのプロパティ名が異なる場合に対応することができる。<br>
<br>
また、大規模なCSVファイルを扱う場合は、ストリーミング処理を使用することを推奨する。<br>
<br>
==== CSVファイルの読み込み ====
===== 同期処理 =====
以下の例では、CsvHelperライブラリを使用して、CSVファイルを同期的に読み込んでいる。
<br>
# StreamReaderクラスを使用して、CSVファイルを開く。
# CsvReaderクラスのインスタンスを生成して、StreamReaderクラスのインスタンスとカルチャ情報を渡す。
# GetRecords<オブジェクト名>メソッドを使用して、CSVファイルの各レコードを任意のオブジェクトにマッピングする。
<br>
また、異なる日付形式やカルチャ設定を指定することもできる。<br>
<br>
// CSVファイルの内容
Id,Name,BirthDate
1,John Doe,1980-01-01
2,Jane Smith,1985-05-15
3,Bob Johnson,1990-12-31
<br>
<syntaxhighlight lang="c#">
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}");
      }
    }
}
</syntaxhighlight>
<br>
===== 非同期処理 =====
以下の例では、CsvHelperライブラリを使用して、CSVファイルをストリーミング処理かつ非同期に読み込んでいる。
<br>
ファイル全体をメモリに読み込む代わりに、1レコードずつ処理するため、大規模なCSVファイルでもメモリ使用量を抑えられる。<br>
ファイルの読み込みと各レコードの処理が非同期で行われるため、アプリケーションの応答性が向上する。<br>
<br>
また、キャンセレーショントークンを追加しているため、長時間の処理をキャンセルすることも可能である。<br>
<br>
<u>await foreach</u>句を使用しているため、.NET Core 3.0以降または.NET 5.0以降で動作することに注意する。<br>
<br>
<syntaxhighlight lang="c#">
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}");
    }
}
</syntaxhighlight>
<br>
==== CSVファイルの書き込み ====
===== 同期処理 =====
以下の例では、CsvHelperライブラリを使用して、CSVファイルへ同期的に作成している。<br>
<br>
System.Text.Encoding.CodePagesを使用しているため、NET Core 3.0以降または.NET 5.0以降が必要となる。<br>
もし、古いバージョンの.NETを使用している場合は、System.Text.Encoding.CodePagesをNuGetからインストールする必要がある。<br>
<br>
<syntaxhighlight lang="c#">
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);
    }
}
</syntaxhighlight>
<br>
// 生成されるCSVファイル
社員番号,氏名,生年月日
1,山田 太郎,1980年01月01日
2,佐藤 花子,1985年05月15日
3,鈴木 一郎,1990年12月31日
<br>
===== 非同期処理 =====
以下の例では、CsvHelperライブラリを使用して、CSVファイルを非同期的に作成している。<br>
<br>
<syntaxhighlight lang="c#">
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);
    }
}
</syntaxhighlight>
<br>
// 生成されるCSVファイル
社員番号,氏名,生年月日
1,山田 太郎,1980年01月01日
2,佐藤 花子,1985年05月15日
3,鈴木 一郎,1990年12月31日
<br><br>
<br><br>