CSVファイルを扱うのに便利な CSVHelper ver 25.0 の Getting Started

プログラム間でデータのやり取りする時に、まだまだ CSV を使うことが多くあります。そんな時、c# なら CSVHelper が非常に役立ちます。

ただ、バージョンアップのスピードが速く仕様の変更も多いので、現時点での最新版 25.0 でのサンプルを挙げておきます。
参照元は [こちら](https://joshclose.github.io/CsvHelper/getting-started/) です。

# 入手方法

Visual Studio のパッケージマネージャーコンソールからインストールします。

```powershell:
PM> Install-Package CsvHelper
```

.NET CLI Console の場合は、シェルでプロジェクトパスに移動し、次のコマンドを実行します。

```shell
> dotnet add package CsvHelper
```

# CSV ファイルの読み込み

　まずは、クラスマッピングを利用した CSV ファイルの読み込み方です。

```c#:読み込みサンプル１
using System;
using System.Globalization;
using System.IO;
using CSVHelper;

namespace test
{
 class Program
 {
   static void Main(string[] args)
   {
     using (var reader = new StreamReader("file.csv"))
     {
       using (var csv = new CsvReader(reader, CultureInfo.InvariantCulture))
       {
         // csv データが行毎に Foo クラスに格納され、IEnumerable<Foo> として
         // records に割り当てられます。
         var records = csv.GetRecords<Foo>();

　　　　　// records は IEnumerable なので、こんな使い方ができます。
         foreach(var i in records)
         {
           Console.WriteLine(i.Id);
         }
       }
     }
   }
   public class Foo
   {
     public int Id { get; set; }
     public string Name { get; set; }
   }
 }
}
```
読み込み対象の CSV ファイルの中身は次の通りです。

```:file.csv
Id,Name
1,Taro
2,Hanako
```

CSVHelper は、CSV ファイルの先頭行をヘッダとみなし、ヘッダ名から Foo のどのプロパティ名に格納するかを判断します。このため、次のような CSV ファイルでも読み込むことができます。

```:file2.csv
Name,Id
Taro,1
Hanako,2
```
 CSVHelper では、ヘッダ名の大文字小文字を区別します。デフォルトは Pascal Case です。もし、Pascal Case でない場合、たとえば全て小文字の場合は、コンフィグで指定することで読み込みが可能になります。

```c#:読み込みサンプル２
using System;
using System.Globalization;
using System.IO;
using CSVHelper;

namespace test
{
 class Program
 {
   static void Main(string[] args)
   {
     var config = new CsvConfiguration(CultureInfo.InvariantCulture)
     {
       //読み取ったヘッダが小文字に変換されるように ToLower() を仕込みます。
       PrepareHeaderForMatch = args => args.Header.ToLower(),
     }; 
     using (var reader = new StreamReader("path\\to\\file.csv"))
     {
       using (var csv = new CsvReader(reader, config))
       {
         // csv データが行毎に Foo クラスに格納され、IEnumerable<Foo> として
         // records に割り当てられます。
         var records = csv.GetRecords<Foo>();

　　　　　// records は IEnumerable なので、こんな使い方ができます。
         foreach(var i in records)
         {
           Console.WriteLine(i.Id);
         }
       }
     }
   }
   public class Foo
   {
     public int Id { get; set; }
     public string Name { get; set; }
   }
 }
}
```

config の PrepareHeaderForMatch で ToLower() を仕込んでおくことで、次のような CSV を読み込むことができます。

```file3.csv
id,name
1,Taro
2,Hanako
```

### CSV にヘッダがない場合

CSV ファイルの１行目にヘッダがない場合は、次のように修正します。

```c#:configの修正
     var config = new CsvConfiguration(CultureInfo.InvariantCulture)
     {
　　   HasHeaderRecord = false,
     }; 
```

## 列と格納先クラスのプロパティ名を属性を使って指定する

　CSVファイルのヘッダと、格納用クラス（例では Foo）のプロパティ名を一致出来ないケースがあると思います。この場合は、格納用クラスのプロパティに属性を設定して指定することが出来ます。この際、CsvHelper.Configuration.Attributes を using で定義しておいてください。
また、Index で指定する場合、初番は 0 ですのでご注意下さい。

```c#:Index属性で読み込み順を指定
   public class Foo
   {
     [Index(0)]
     public int Id { get; set; }
     [Index(1)]
     public string Name { get; set; }
   }
```

```c#:Name属性でヘッダ名を指定
   public class Foo
   {
     [Name("id")]
     public int Id { get; set; }
     [Name("name")]
     public string Name { get; set; }
   }
```
## 列と格納先クラスのプロパティ名をクラスマップを使って指定する

　以前のバージョンからあった方法で、格納先クラスをベースにマップクラスを定義し、読み込み順序を定義します。サンプルでは、Foo に対応する FooMap を定義しています。
なお、ClassMap クラスは CsvHelper.Configuration に定義されていますので、using で宣言しておきます。

```c#:読み込みサンプル３
using System;
using System.Globalization;
using System.IO;
using CSVHelper;
using CsvHelper.Configuration;

namespace test
{
 class Program
 {
   static void Main(string[] args)
   {
     using (var reader = new StreamReader("path\\to\\file.csv"))
     {
       using (var csv = new CsvReader(reader, config))
       {
         //クラスマップを使って読み込み順序を指定します
         csv.Context.RegisterClassMap<FooMap>();

         // csv データが行毎に Foo クラスに格納され、IEnumerable<Foo> として
         // records に割り当てられます。
         var records = csv.GetRecords<Foo>();

　　　　　// records は IEnumerable なので、こんな使い方ができます。
         foreach(var i in records)
         {
           Console.WriteLine(i.Id);
         }
       }
     }
   }
   public class Foo
   {
     public int Id { get; set; }
     public string Name { get; set; }
   }
   public class FooMap: ClassMap<Foo>
   {
     public FooMap()
     {
        Map(m => m.Id).Name("id");
        Map(m => m.Name).Name("name");
     }
   }
 }
}
```

## １行読み込む毎に処理を行いたい

　csv.GetRecords() を使うと CSV を丸ごと読み込むことができました。しかし、業務プログラムなんかだと、１行読み込む毎にコンソールに出力したり、チェックをかけたりすることが多いと思います。

この場合は、GetRecords() ではなく GetRecord() を使います。

```c#:読み込みサンプル４
using System;
using System.Globalization;
using System.IO;
using CSVHelper;

namespace test
{
 class Program
 {
   static void Main(string[] args)
   {
     using (var reader = new StreamReader("path\\to\\file.csv"))
     {
       using (var csv = new CsvReader(reader, CultureInfo.InvariantCulture))
       {
         //読み込み開始準備を行います
         csv.Read();
         //ヘッダを読み込みます
         csv.ReadHeader();
         //行毎に読み込みと処理を行います
         while (csv.Read())
         {
           var record = csv.GetRecord<Foo>();
           Console.WriteLine(record.Id);
         }
       }
     }
   }
   public class Foo
   {
     public int Id { get; set; }
     public string Name { get; set; }
   }
 }
}
```


# CSV ファイルの出力

　次に配列に格納されたデータを CSV ファイルに出力します。データは records にあるとします。records は Foo クラスの配列です。これを CSV ファイルに出力します。


```c#:書き込みサンプル１
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using CSVHelper;

namespace test
{
 class Program
 {
   static void Main(string[] args)
   {
     var records = new List<Foo>
     {
       new Foo { Id = 1, Name = "one" },
       new Foo { Id = 2, Name = "two" },
     };

     using (var writer = new StreamWriter("file.csv"))
     {
       using (var csv = new CsvWriter(writer, CultureInfo.InvariantCulture))
       {
         //この１行で保存ができる
         csv.WriteRecords(records);
       }
     }
   }
   public class Foo
   {
     public int Id { get; set; }
     public string Name { get; set; }
   }
 }
}
```

## ヘッダを付けずに出力する

　ヘッダなしの CSV ファイルを読み込む時と同様に、ヘッダを出力しないように config で指定し、WriterRecords()を実行します。

```c#:書き込みサンプル２
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using CSVHelper;

namespace test
{
 class Program
 {
   static void Main(string[] args)
   {
     var records = new List<Foo>
     {
       new Foo { Id = 1, Name = "one" },
       new Foo { Id = 2, Name = "two" },
     };

     var config = new CsvConfiguration(CultureInfo.InvariantCulture)
     {
       //ヘッダを出力しないように指定
　　   HasHeaderRecord = false,
     }; 
     using (var writer = new StreamWriter("file.csv"))
     {
       using (var csv = new CsvWriter(writer, config))
       {
         csv.WriteRecords(records);
       }
     }
   }
   public class Foo
   {
     public int Id { get; set; }
     public string Name { get; set; }
   }
 }
}
```
## 列と格納先クラスのプロパティ名を属性を使って指定する

　こちらも上の読み込みの時と同様に、格納先クラス「Foo」に属性を指定します。Index 属性だと番号で指定できます。Name 属性だと、ヘッダ名で指定できます。Index 属性の場合のサンプルは次の通りです。

```c#:書き込みサンプル３
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using CsvHelper;
using CsvHelper.Configuration;
using CsvHelper.Configuration.Attributes;

namespace test
{
 class Program
 {
   static void Main(string[] args)
   {
     var records = new List<Foo>
     {
       new Foo { Id = 1, Name = "one" },
       new Foo { Id = 2, Name = "two" },
     };

     using (var writer = new StreamWriter("file.csv"))
     {
       using (var csv = new CsvWriter(writer , config))
       {
         csv.WriteRecords(records);
       }
     }
   }
   public class Foo
   {
     [Index(0)]
     public int Id { get; set; }
     [Index(1)]
     public string Name { get; set; }
   }
 }
}
```

## 列と格納先クラスのプロパティ名をクラスマップを使って指定する

　こちらも読み込み時と同様です。ClassMap を使って Mapクラスを定義します。全データを出力するなら Index や Name 属性の方が便利でお勧めです。
　読み込みと書き込みで列の位置が異なるなら、読み込み用のクラスマップと書き込み用のクラスマップを用意することで実現できます。

```c#:書き込みサンプル４
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using CsvHelper;
using CsvHelper.Configuration;

namespace test
{
 class Program
 {
   static void Main(string[] args)
   {
     var records ;
     using (var reader = new StreamReader("file.csv"))
     {
       using (var csv = new CsvReader(reader, CultureInfo.InvariantCulture))
       {
         csv.Context.RegisterClassMap<ReadFooMap>();
         records = csv.GetRecords<Foo>();
       }
     }

     using (var writer = new StreamWriter("fileout.csv"))
     {
       var config = new CsvConfiguration(CultureInfo.InvariantCulture)
       {
         csv.Context.RegisterClassMap<WriteFooMap>();
         csv.WriteRecords(records);
       }
     }
   }
   public class Foo
   {
     public int Id { get; set; }
     public string Name { get; set; }
   }
   public class ReadFooMap : ClassMap<Foo>
   {
     public ReadFooMap ()
     {
        Map(m => m.Id).Name("id");
        Map(m => m.Name).Name("name");
     }
   }
   public class WriteFooMap : ClassMap<Foo>
   {
     public WriteFooMap()
     {
        Map(m => m.Name).Name("name");
        Map(m => m.Id).Name("id");
     }
   }
 }
}
```

## １行出力する毎に処理を行いたい

　WriteRecords() だと一気に CSV データを出力できました。行ごとに処理を行う場合は、WriteRecord() を使います。読み込みの場合と同じですね。

```c#:書き込みサンプル４
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using CsvHelper;
using CsvHelper.Configuration;

namespace test
{
 class Program
 {
   static void Main(string[] args)
   {
     var records = new List<Foo>
     {
       new Foo { Id = 1, Name = "one" },
       new Foo { Id = 2, Name = "two" },
     };

     using (var writer = new StreamWriter("file.csv"))
     {
       var config = new CsvConfiguration(CultureInfo.InvariantCulture)
       {
         csv.WriteHeader<Foo>();
         csv.NextRecord();
         foreach (var record in records)
         {
           csv.WriteRecord(record);
           csv.NextRecord();
         }
       }
     }
   }
   public class Foo
   {
     public int Id { get; set; }
     public string Name { get; set; }
   }
 }
}
```

# ここまでが Getting Started 

 CSVHelper の Getting Start はここまでです。
 ２種類の列レイアウトのある複雑な CSV を読んだり、データを bool 値として読み込むなど、様々な機能があります。

また、読み込んだデータを配列ではなく、DataTable に格納する事もできます。DataTable に格納できれば、そのまま BulkCopy を使って SQL Server のに高速にインポートできます。

興味がわいた方は、下のリンクをたどってください。

[CSVHelper の Example のページ](https://joshclose.github.io/CsvHelper/examples/)

## 関連記事

[CSVHelper の小ネタ 出力時に日付を指定のフォーマットで出力する](https://qiita.com/tnishiki/items/ab52dbcfb3640376ab4c)