【CSV読み込み】TextFieldParserではなく、CsvParserを使用する

#2020/02/19追記
CsvParserを使用せずとも、CsvReaderで可能でした。
[CSVの解説と各プログラミング言語での実装例](https://qiita.com/mima_ita/items/f6b68d23ec9727badf96#comments)

# CSVパース時の懸念点
CSVをパースする際、単純に1行ずつ読み込んで、カンマ「,」でSplitするだけで事足りるようなデータであればいいのですが、実際はそうともいかないので、以下のような考慮が必要になるかと思います。

※前提　区切り文字：カンマ「,」
　　　　囲み文字：ダブルクオーテーション「"」
　　　　各行の列数は同じ

- データ中にカンマが存在する(区切り文字に指定した文字がある)

```
"a","b","c,d,e"
```
- データ中にダブルクォーテーションが存在する(ダブルクォーテーションを二重化してエスケープされている)

```
"a","""b","c"
```
- データ中に改行が存在する

```
"a","b
ccc","d"
```
- 囲んだパターンと囲まないパターンが混在

```
"a",b,"c,d,e"
```

他には、データ中にタブ「\t」が存在する等・・・

# まずTextFieldParserを使用してみましたが・・・
こういったデータを考慮しながら自前でパースしていくのはなかなか大変なので、というか私は考えることすらやめたので・・・
(いつか挑戦したい)
便利なクラスがないか昔ネットで探したところ、TextFieldParserが良さげのようでした。
[TextFiledParser](https://docs.microsoft.com/ja-jp/dotnet/api/microsoft.visualbasic.fileio.textfieldparser?view=netframework-4.8)

.Netのクラスなので、他のオープンソースのパーサーと比べて、動作に信頼が持て、コンプライアンス的にみても良いと思っていました。


実際に上記データを読み込んでみると綺麗にパースできます。

**使用するCSV**

```CSV
"ヘッダー1","ヘッダー2","ヘッダー3"
"a","b","c,d,e"
"a","""b","c"
"a","b
ccc","d"
"a",b,"c,d,e"
```

**コード**

```C#
using Microsoft.VisualBasic.FileIO;
using System;
using System.Linq;
using System.Text;

namespace TestProject.CSVParseTest
{
    public class CSVParseTest
    {
        public void ReadCsv()
        {
            using (var parser = new TextFieldParser(@".\test.csv", Encoding.UTF8))
            {
                // 区切り文字
                parser.Delimiters = new string[] { "," };

                // 囲み文字あり
                parser.HasFieldsEnclosedInQuotes = true;

                while (!parser.EndOfData)
                {
                    var values = parser.ReadFields();
                    values.ToList().ForEach(v =>
                    {
                        // わかりやすいようにパイプを入れる
                        Console.Write(v + "|");
                    });

                    // わかりやすいように改行
                    Console.WriteLine();
                }
            }
        }
    }
}
```
```C#
using System;

namespace TestProject.CSVParseTest
{
    public class Program
    {
        public static void Main(string[] args)
        {
            try
            {
                var pTest = new CSVParseTest();
                pTest.ReadCsv();
            }
            catch(Exception err)
            {
                Console.WriteLine(err.Message);
            }
            finally
            {
                Console.Read();
            }
        }
    }
}
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/526982/92aeba94-53fb-55fc-a077-e13c75a37941.png)


上記の結果に満足していたのですが、次のようなデータの場合に、意図しない動きとなってしまいました。

**使用するCSV**　※データ中に空の改行が入る

```
"ヘッダー1","ヘッダー2","ヘッダー3"
"a","b","c,d,e"
"a","""b","c"
"a","b

gggg

ffff

ccc","d"
"a",b,"c,d,e"
```
**結果**
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/526982/0ee53e08-eba3-a3b1-8b56-56f36ef5e66b.png)
データ中であっても、空行が削除されてしまうようです。
掲示板などの備考欄等のテキストには、よく空行を入れてみた目を整えることがあるかとおもいます。
そういったデータを扱えなくなるのは困るので不採用としました。

# CsvHelperのCsvParserクラスを使用する
他をあたることになり、人気のCsvHelperを検討していました。
[CsvHelper](https://joshclose.github.io/CsvHelper/)

ただ、これを利用する場合、CSVのフォーマットに沿ったエンティティのクラスを用意する必要があり、
CSVの列数が後から変更になったりすると、都度プログラムソースも修正しなければならないと思ったので躊躇していました。

TextFiledParserのように１行分の文字を区切り文字でsplitして配列で返してくれたらなぁ・・・
かつ、空行も再現してくれたらなぁ・・・
なんて思っていたら、CsvHelper.CsvParserのReadメソッドがそれでした。

```C#
using CsvHelper;
using System;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;

namespace TestProject.CSVParseTest
{
    public class CSVParseTest2
    {
        public void ReadCsv()
        {
            using(var stream = new StreamReader(@".\test.csv", Encoding.UTF8))
            using (var parser = new CsvParser(stream, CultureInfo.InvariantCulture))
            {
                string[] line;
                while ((line = parser.Read()) != null)
                {
                    line.ToList().ForEach(v =>
                    {
                        // わかりやすいようにパイプを入れる
                        Console.Write(v + "|");
                    });

                    // わかりやすいように改行
                    Console.WriteLine();
                }
            }
        }
    }
}
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/526982/4bf88bf2-8edd-3315-d205-701913746ce5.png)

「TextFieldParser + 空行が削除されない」が実現できました。

# おわりに
CsvHelperの中はおそらく１文字ずつ処理して、パースしているんだと思います。
いつか中身を見て勉強してみたいです。

誤認や、他の方法で解決できる等、ありましたら指摘いただけるとありがたいです。