C#でCSVをListに読み込む方法|初心者向けに実装例付きで解説

はじめに

C#でCSVファイルを読み込み、Listに格納する処理は、業務アプリケーションやデータ取り込み機能でよく使われます。

たとえば、次のようなCSVがあるとします。

csv
Id,Name,Age
1,田中,25
2,佐藤,30
3,鈴木,28

このCSVをC#で読み込み、List<string[]>List<Person>のような形に変換できると、画面表示、検索、集計、データベース登録などに利用しやすくなります。

この記事では、C#でCSVをListに読み込む基本から、StreamReaderを使ったシンプルな実装、クラスのListへの変換、CsvHelperを使った実務向けの方法まで、初心者向けに実装例付きで解説します。

1. C#でCSVをListに読み込む基本

1-1. CSVをListに読み込むとは何か

CSVとは、Comma Separated Valuesの略で、カンマ区切りでデータを表現するテキストファイルです。

C#でCSVをListに読み込むとは、CSVファイルの各行を読み取り、プログラム上で扱いやすいList形式に格納することです。

たとえば、CSVの1行を文字列配列として扱う場合、次のようなイメージになります。

C#
List<string[]> rows = new List<string[]>();

CSVの各行を読み込み、カンマで分割してstring[]にし、それをList<string[]>へ追加していきます。

C#
rows.Add(new string[] { "1", "田中", "25" });
rows.Add(new string[] { "2", "佐藤", "30" });

このようにListに格納しておくことで、あとからループ処理で取り出したり、条件に一致するデータを検索したりできます。

1-2. CSV読み込みでよく使うListの種類

C#でCSVをListに読み込む場合、よく使われるListの形は主に次の3つです。

C#
List<string[]>

1行分のCSVデータを文字列配列として格納する方法です。シンプルで初心者にもわかりやすく、列数が固定されているCSVに向いています。

C#
List<List<string>>

1行分のデータをList<string>として格納する方法です。列数が変動する可能性がある場合や、Listとして柔軟に操作したい場合に使えます。

C#
List<Person>

CSVの1行をクラスの1オブジェクトとして格納する方法です。実務ではこの形がよく使われます。たとえば、IdNameAgeを持つCSVであれば、Personクラスを作成してList<Person>に変換します。

C#
public class Person
{
public int Id { get; set; }
public string Name { get; set; } = "";
public int Age { get; set; }
}

初心者のうちは、まずList<string[]>でCSVを読み込む方法を理解し、その後にList<クラス名>へ変換する方法を学ぶとスムーズです。

1-3. 初心者が最初に理解すべき処理の流れ

C#でCSVをListに読み込む基本的な流れは、次のとおりです。

CSVファイルを開く

1行ずつ読み込む

カンマで分割する

Listに追加する

Listの中身を利用する

実装では、主に次のクラスやメソッドを使います。

C#
StreamReader
ReadLine()
Split(',')
List<T>
Add()

StreamReaderでファイルを開き、ReadLine()で1行ずつ読み込みます。読み込んだ行をSplit(',')で分割し、Listに追加します。

ただし、単純なSplit(',')には注意点もあります。たとえば、値の中にカンマが含まれるCSVや、ダブルクォーテーションで囲まれたCSVは正しく分割できない場合があります。

まずはシンプルなCSVで基本を理解し、実務ではCsvHelperなどのライブラリを使うのがおすすめです。

2. C#でCSVをListに読み込む最もシンプルな方法

2-1. StreamReaderを使ってCSVファイルを読み込む

C#でCSVファイルを読み込む基本的な方法は、StreamReaderを使うことです。

次のようなCSVファイルを例にします。

csv
1,田中,25
2,佐藤,30
3,鈴木,28

ファイル名をpeople.csvとした場合、C#では次のように読み込めます。

C#
using System;
using System.Collections.Generic;
using System.IO;

class Program
{
static void Main()
{
string filePath = "people.csv";

using StreamReader reader = new StreamReader(filePath);

string? line;
while ((line = reader.ReadLine()) != null)
{
Console.WriteLine(line);
}
}
}

ReadLine()はCSVファイルから1行ずつ文字列を読み込みます。ファイルの最後まで読み込むとnullが返るため、while文で最後まで処理できます。

usingを使うことで、ファイルの読み込みが終わったあとに自動でファイルが閉じられます。

2-2. 1行ずつ読み込んでList<string[]>に格納する

CSVをListに読み込むには、1行ずつ読み取った文字列を分割して、Listに追加します。

C#
using System;
using System.Collections.Generic;
using System.IO;

class Program
{
static void Main()
{
string filePath = "people.csv";

List<string[]> csvList = new List<string[]>();

using StreamReader reader = new StreamReader(filePath);

string? line;
while ((line = reader.ReadLine()) != null)
{
string[] values = line.Split(',');
csvList.Add(values);
}

Console.WriteLine("CSVの読み込みが完了しました。");
}
}

このコードでは、CSVの1行をstring[]として扱い、それをList<string[]>に追加しています。

CSVの内容が次のような場合、

csv
1,田中,25
2,佐藤,30
3,鈴木,28

Listの中身は次のようなイメージになります。

C#
csvList[0] = { "1", "田中", "25" }
csvList[1] = { "2", "佐藤", "30" }
csvList[2] = { "3", "鈴木", "28" }

2-3. Splitでカンマ区切りのデータを分割する実装例

Split(',')を使うと、カンマ区切りの文字列を簡単に配列へ分割できます。

C#
string line = "1,田中,25";
string[] values = line.Split(',');

Console.WriteLine(values[0]); // 1
Console.WriteLine(values[1]); // 田中
Console.WriteLine(values[2]); // 25

CSVファイル全体をListに読み込む実装は次のとおりです。

C#
using System;
using System.Collections.Generic;
using System.IO;

class Program
{
static void Main()
{
string filePath = "people.csv";
List<string[]> people = new List<string[]>();

using (StreamReader reader = new StreamReader(filePath))
{
string? line;

while ((line = reader.ReadLine()) != null)
{
string[] columns = line.Split(',');
people.Add(columns);
}
}

foreach (string[] person in people)
{
Console.WriteLine($"ID: {person[0]}, 名前: {person[1]}, 年齢: {person[2]}");
}
}
}

実行結果は次のようになります。

ID: 1, 名前: 田中, 年齢: 25
ID: 2, 名前: 佐藤, 年齢: 30
ID: 3, 名前: 鈴木, 年齢: 28

この方法はとても簡単ですが、次のようなCSVには向いていません。

csv
1,"田中,太郎",25

この場合、名前の中にカンマが含まれているため、単純なSplit(',')では正しく分割できません。

2-4. 読み込んだListの中身を確認する方法

CSVをListに読み込んだあとは、foreachで中身を確認できます。

C#
foreach (string[] row in csvList)
{
foreach (string value in row)
{
Console.Write(value + " ");
}

Console.WriteLine();
}

列番号を指定して表示することもできます。

C#
foreach (string[] row in csvList)
{
Console.WriteLine($"1列目: {row[0]}, 2列目: {row[1]}, 3列目: {row[2]}");
}

ただし、CSVの行によって列数が不足している場合、row[2]のように存在しない列へアクセスするとエラーになります。

安全に処理するなら、列数を確認してからアクセスします。

C#
foreach (string[] row in csvList)
{
if (row.Length >= 3)
{
Console.WriteLine($"ID: {row[0]}, 名前: {row[1]}, 年齢: {row[2]}");
}
}

初心者のうちは、CSVを読み込んだあとにListの中身を必ず確認すると、想定どおりに読み込めているか判断しやすくなります。

3. ヘッダー付きCSVをListに読み込む方法

3-1. 1行目のヘッダーをスキップする方法

CSVには、1行目に列名が入っていることがよくあります。

csv
Id,Name,Age
1,田中,25
2,佐藤,30
3,鈴木,28

このようなCSVでは、1行目のId,Name,Ageはデータではなくヘッダーです。そのため、Listにデータだけを入れたい場合は、1行目をスキップします。

最も簡単な方法は、最初にReadLine()を1回呼び出してヘッダー行を読み飛ばすことです。

C#
using System;
using System.Collections.Generic;
using System.IO;

class Program
{
static void Main()
{
string filePath = "people.csv";
List<string[]> people = new List<string[]>();

using StreamReader reader = new StreamReader(filePath);

// 1行目のヘッダーを読み飛ばす
reader.ReadLine();

string? line;
while ((line = reader.ReadLine()) != null)
{
string[] columns = line.Split(',');
people.Add(columns);
}

foreach (string[] person in people)
{
Console.WriteLine($"ID: {person[0]}, 名前: {person[1]}, 年齢: {person[2]}");
}
}
}

この方法なら、Listにはデータ行だけが格納されます。

3-2. ヘッダー行とデータ行を分けて扱う実装例

ヘッダー行もあとで使いたい場合は、ヘッダー用の変数に保存しておくと便利です。

C#
using System;
using System.Collections.Generic;
using System.IO;

class Program
{
static void Main()
{
string filePath = "people.csv";

string[]? headers = null;
List<string[]> rows = new List<string[]>();

using StreamReader reader = new StreamReader(filePath);

string? headerLine = reader.ReadLine();
if (headerLine != null)
{
headers = headerLine.Split(',');
}

string? line;
while ((line = reader.ReadLine()) != null)
{
string[] values = line.Split(',');
rows.Add(values);
}

if (headers != null)
{
Console.WriteLine("ヘッダー:");
foreach (string header in headers)
{
Console.WriteLine(header);
}
}

Console.WriteLine("データ:");
foreach (string[] row in rows)
{
Console.WriteLine(string.Join(", ", row));
}
}
}

ヘッダー行を別に保持しておくと、列番号だけでなく列名を意識した処理ができます。

たとえば、headers[0]Idheaders[1]Nameheaders[2]Ageというように対応関係を確認できます。

3-3. ヘッダー名を使ってデータを扱う場合の注意点

ヘッダー名を使ってCSVのデータを扱う場合は、列の順番が変わっても処理できるというメリットがあります。

ただし、次の点に注意が必要です。

ヘッダー名の表記ゆれがあると、正しく列を見つけられません。

csv
Id,Name,Age

csv
ID,名前,年齢

では、ヘッダー名が異なるため、同じコードでは処理できない場合があります。

また、ヘッダーに余分な空白が入ることもあります。

csv
Id, Name, Age

この場合、Nameではなく Nameのように先頭に空白が含まれる可能性があります。必要に応じてTrim()で空白を取り除きます。

C#
string[] headers = headerLine.Split(',')
.Select(h => h.Trim())
.ToArray();

Selectを使う場合は、次の名前空間が必要です。

C#
using System.Linq;

ヘッダー名を使って列番号を取得する例は次のとおりです。

C#
int nameIndex = Array.IndexOf(headers, "Name");

foreach (string[] row in rows)
{
if (nameIndex >= 0 && row.Length > nameIndex)
{
Console.WriteLine(row[nameIndex]);
}
}

ヘッダー付きCSVを扱う場合は、列の順番だけに依存しない実装にすると、CSVの仕様変更に強くなります。

4. CSVをクラスのListに変換して読み込む方法

4-1. CSVデータに対応するクラスを作成する

実務では、CSVをList<string[]>のまま扱うよりも、クラスのListに変換することが多いです。

たとえば、次のCSVを読み込むとします。

csv
Id,Name,Age
1,田中,25
2,佐藤,30
3,鈴木,28

このCSVに対応するクラスを作成します。

C#
public class Person
{
public int Id { get; set; }
public string Name { get; set; } = "";
public int Age { get; set; }
}

このようにクラスを作ることで、CSVの各列をプロパティとして扱えるようになります。

C#
person.Id
person.Name
person.Age

row[0]row[1]のような列番号で扱うよりも、意味がわかりやすく、保守しやすいコードになります。

4-2. List<クラス名>にCSVデータを格納する実装例

CSVを読み込み、List<Person>に変換する例を見てみましょう。

C#
using System;
using System.Collections.Generic;
using System.IO;

public class Person
{
public int Id { get; set; }
public string Name { get; set; } = "";
public int Age { get; set; }
}

class Program
{
static void Main()
{
string filePath = "people.csv";
List<Person> people = new List<Person>();

using StreamReader reader = new StreamReader(filePath);

// ヘッダーをスキップ
reader.ReadLine();

string? line;
while ((line = reader.ReadLine()) != null)
{
string[] columns = line.Split(',');

if (columns.Length >= 3)
{
Person person = new Person
{
Id = int.Parse(columns[0]),
Name = columns[1],
Age = int.Parse(columns[2])
};

people.Add(person);
}
}

foreach (Person person in people)
{
Console.WriteLine($"{person.Id}: {person.Name} ({person.Age}歳)");
}
}
}

実行結果は次のようになります。

1: 田中 (25歳)
2: 佐藤 (30歳)
3: 鈴木 (28歳)

このようにList<Person>として読み込むと、CSVデータをオブジェクトとして扱えるようになります。

4-3. stringからintやDateTimeへ型変換する方法

CSVから読み込んだ値は、基本的に文字列です。そのため、数値や日付として扱いたい場合は型変換が必要です。

文字列をintに変換する場合は、int.Parse()を使います。

C#
int age = int.Parse("25");

ただし、変換できない文字列が入っていると例外が発生します。

C#
int age = int.Parse("abc"); // エラー

安全に変換したい場合は、int.TryParse()を使います。

C#
if (int.TryParse(columns[2], out int age))
{
Console.WriteLine(age);
}
else
{
Console.WriteLine("年齢の形式が正しくありません。");
}

日付を扱う場合は、DateTime.Parse()またはDateTime.TryParse()を使います。

C#
DateTime birthday = DateTime.Parse("2000/01/15");

安全に変換するなら、次のように書きます。

C#
if (DateTime.TryParse(columns[3], out DateTime birthday))
{
Console.WriteLine(birthday);
}
else
{
Console.WriteLine("日付の形式が正しくありません。");
}

CSV読み込みでは、空文字や不正な値が入ることもあるため、実務ではParseよりもTryParseを使う方が安全です。

4-4. クラス化するメリットと使いどころ

CSVをクラスのListに変換するメリットは、データの意味が明確になることです。

List<string[]>の場合、次のように列番号でアクセスします。

C#
row[0]
row[1]
row[2]

これだけでは、どの列が何を表しているのかわかりにくいです。

一方、List<Person>に変換すると、次のように書けます。

C#
person.Id
person.Name
person.Age

プロパティ名を見れば、何のデータを扱っているのかすぐにわかります。

また、クラス化するとLINQを使った検索や並び替えもしやすくなります。

C#
var adults = people.Where(p => p.Age >= 20).ToList();
var sorted = people.OrderBy(p => p.Age).ToList();

クラス化は、次のような場面に向いています。

CSVの列の意味が決まっている場合、読み込んだデータを何度も利用する場合、検索や集計を行う場合、データベース登録の前処理として使う場合などです。

簡単な確認だけならList<string[]>でも十分ですが、実務で使うならList<クラス名>に変換する方法を覚えておくと便利です。

5. CsvHelperを使ってCSVをListに読み込む方法

5-1. CsvHelperとは

CsvHelperは、C#でCSVを扱うための有名なライブラリです。

標準のSplit(',')を使った方法では、次のようなCSVを正しく処理できない場合があります。

csv
Id,Name,Note
1,田中,"東京都,新宿区"

このCSVでは、Note列の中にカンマが含まれています。単純にSplit(',')すると、東京都新宿区が別の列として扱われてしまいます。

CsvHelperを使うと、ダブルクォーテーションで囲まれた値、ヘッダー付きCSV、型変換、クラスへのマッピングなどを簡単に扱えます。

実務でCSVを読み込む場合は、単純なCSV以外にも対応しやすいため、CsvHelperを使う方法がおすすめです。

5-2. CsvHelperのインストール方法

CsvHelperはNuGetからインストールできます。

.NET CLIを使う場合は、プロジェクトフォルダで次のコマンドを実行します。

Bash
dotnet add package CsvHelper

Visual Studioを使う場合は、NuGetパッケージマネージャーからCsvHelperを検索してインストールできます。

CsvHelperを使うコードでは、主に次の名前空間を使用します。

C#
using CsvHelper;
using System.Globalization;

ヘッダー名とプロパティ名を対応付ける場合は、次の名前空間も使います。

C#
using CsvHelper.Configuration;

5-3. CsvHelperでCSVをList<T>に変換する実装例

次のCSVを読み込む例を見てみましょう。

csv
Id,Name,Age
1,田中,25
2,佐藤,30
3,鈴木,28

CSVに対応するクラスを作成します。

C#
public class Person
{
public int Id { get; set; }
public string Name { get; set; } = "";
public int Age { get; set; }
}

CsvHelperでCSVをList<Person>に読み込むコードは次のとおりです。

C#
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using CsvHelper;

public class Person
{
public int Id { get; set; }
public string Name { get; set; } = "";
public int Age { get; set; }
}

class Program
{
static void Main()
{
string filePath = "people.csv";

using StreamReader reader = new StreamReader(filePath);
using CsvReader csv = new CsvReader(reader, CultureInfo.InvariantCulture);

List<Person> people = csv.GetRecords<Person>().ToList();

foreach (Person person in people)
{
Console.WriteLine($"{person.Id}: {person.Name} ({person.Age}歳)");
}
}
}

CsvHelperでは、CSVのヘッダー名とクラスのプロパティ名が一致していれば、自動的に値をセットしてくれます。

csv
Id,Name,Age

C#
public int Id { get; set; }
public string Name { get; set; }
public int Age { get; set; }

が対応しているため、GetRecords<Person>()だけでList<Person>に変換できます。

5-4. ヘッダー名とプロパティ名が異なる場合の対応方法

CSVのヘッダー名とC#のプロパティ名が異なる場合は、マッピングクラスを作成します。

たとえば、CSVが次のようになっているとします。

csv
社員ID,氏名,年齢
1,田中,25
2,佐藤,30

C#側のクラスは英語のプロパティ名にしたい場合があります。

C#
public class Employee
{
public int Id { get; set; }
public string Name { get; set; } = "";
public int Age { get; set; }
}

この場合、CsvHelperのClassMapを使って対応関係を定義します。

C#
using CsvHelper.Configuration;

public class EmployeeMap : ClassMap<Employee>
{
public EmployeeMap()
{
Map(m => m.Id).Name("社員ID");
Map(m => m.Name).Name("氏名");
Map(m => m.Age).Name("年齢");
}
}

読み込み時にマッピングを登録します。

C#
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using CsvHelper;
using CsvHelper.Configuration;

public class Employee
{
public int Id { get; set; }
public string Name { get; set; } = "";
public int Age { get; set; }
}

public class EmployeeMap : ClassMap<Employee>
{
public EmployeeMap()
{
Map(m => m.Id).Name("社員ID");
Map(m => m.Name).Name("氏名");
Map(m => m.Age).Name("年齢");
}
}

class Program
{
static void Main()
{
string filePath = "employees.csv";

using StreamReader reader = new StreamReader(filePath);
using CsvReader csv = new CsvReader(reader, CultureInfo.InvariantCulture);

csv.Context.RegisterClassMap<EmployeeMap>();

List<Employee> employees = csv.GetRecords<Employee>().ToList();

foreach (Employee employee in employees)
{
Console.WriteLine($"{employee.Id}: {employee.Name} ({employee.Age}歳)");
}
}
}

ヘッダー名が日本語の場合や、CSVの列名とクラスのプロパティ名を分けたい場合は、この方法が便利です。

6. CSV読み込みでよくあるエラーと対処法

6-1. ファイルが見つからない場合の対処法

CSV読み込みでよくあるエラーのひとつが、ファイルが見つからないケースです。

C#
string filePath = "people.csv";

このように相対パスを指定した場合、実行時のカレントディレクトリにpeople.csvが存在しないとエラーになります。

事前にファイルの存在を確認すると安全です。

C#
using System;
using System.IO;

class Program
{
static void Main()
{
string filePath = "people.csv";

if (!File.Exists(filePath))
{
Console.WriteLine("CSVファイルが見つかりません。");
return;
}

using StreamReader reader = new StreamReader(filePath);

string? line;
while ((line = reader.ReadLine()) != null)
{
Console.WriteLine(line);
}
}
}

ファイルパスの指定ミスを防ぐには、絶対パスを使って確認するのも有効です。

C#
string filePath = @"C:\data\people.csv";

Windowsのパスでは\を使うため、文字列の前に@を付けると書きやすくなります。

6-2. 文字化けする場合のエンコーディング指定

CSVを読み込んだときに日本語が文字化けする場合は、文字コードが原因の可能性があります。

C#のStreamReaderでは、エンコーディングを指定できます。

UTF-8のCSVを読み込む場合は、次のように書きます。

C#
using System.Text;

using StreamReader reader = new StreamReader("people.csv", Encoding.UTF8);

Excelなどで作成したCSVでは、Shift-JISが使われていることがあります。その場合は、次のように指定します。

C#
using System.Text;

Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);

using StreamReader reader = new StreamReader("people.csv", Encoding.GetEncoding("shift_jis"));

.NET Coreや.NET 5以降でShift-JISを扱う場合、CodePagesEncodingProviderを使うために、必要に応じて次のパッケージを追加します。

Bash
dotnet add package System.Text.Encoding.CodePages

文字化けした場合は、まずCSVファイルの文字コードを確認し、StreamReaderで適切なエンコーディングを指定しましょう。

6-3. 空行や空データがある場合の処理

CSVには、空行が含まれていることがあります。

csv
Id,Name,Age
1,田中,25

2,佐藤,30

空行をそのまま処理すると、列数が足りずにエラーになることがあります。

空行をスキップするには、string.IsNullOrWhiteSpace()を使います。

C#
string? line;
while ((line = reader.ReadLine()) != null)
{
if (string.IsNullOrWhiteSpace(line))
{
continue;
}

string[] columns = line.Split(',');
csvList.Add(columns);
}

また、空データがある場合にも注意が必要です。

csv
Id,Name,Age
1,田中,25
2,,30
3,鈴木,

このようなCSVでは、名前や年齢が空になっています。型変換する前に、空文字をチェックしましょう。

C#
string name = columns[1];

if (string.IsNullOrWhiteSpace(name))
{
Console.WriteLine("名前が空です。");
}

数値に変換する場合は、int.TryParse()を使うと安全です。

C#
if (int.TryParse(columns[2], out int age))
{
Console.WriteLine(age);
}
else
{
Console.WriteLine("年齢が空、または数値ではありません。");
}

6-4. カンマを含む文字列を正しく読み込めない場合

単純なSplit(',')では、値の中にカンマが含まれるCSVを正しく読み込めません。

たとえば、次のCSVを考えます。

csv
Id,Name,Address
1,田中,"東京都,新宿区"

この行をSplit(',')で分割すると、次のように分かれてしまいます。

1
田中
"東京都
新宿区"

本来は"東京都,新宿区"を1つの値として扱いたいのに、途中のカンマで分割されてしまいます。

このようなCSVを正しく読み込むには、CsvHelperなどのCSV専用ライブラリを使うのがおすすめです。

C#
using System.Globalization;
using CsvHelper;

using StreamReader reader = new StreamReader("people.csv");
using CsvReader csv = new CsvReader(reader, CultureInfo.InvariantCulture);

List<Person> people = csv.GetRecords<Person>().ToList();

CSVには、カンマ、ダブルクォーテーション、改行を含む値など、単純な文字列分割では対応しづらいケースがあります。実務で扱うCSVでは、CsvHelperを使う方が安全です。

6-5. 型変換エラーが発生する場合の対処法

CSVの値をintDateTimeに変換するとき、形式が正しくないとエラーになります。

C#
int age = int.Parse(columns[2]);

columns[2]に空文字やabcのような文字列が入っていると、変換に失敗します。

安全に処理するには、TryParseを使います。

C#
if (!int.TryParse(columns[2], out int age))
{
Console.WriteLine("年齢の形式が正しくありません。");
continue;
}

日付の場合も同様です。

C#
if (!DateTime.TryParse(columns[3], out DateTime createdAt))
{
Console.WriteLine("日付の形式が正しくありません。");
continue;
}

型変換に失敗した行をスキップするのか、初期値を入れるのか、エラーとして処理を止めるのかは、システムの要件によって決めます。

たとえば、年齢が不正な場合に0を入れるなら、次のように書けます。

C#
int age = int.TryParse(columns[2], out int parsedAge) ? parsedAge : 0;

ただし、実務では不正なデータを見逃さないように、ログを出すなどの対策も重要です。

7. 実務で使いやすいCSV読み込みコードの作り方

7-1. CSV読み込み処理をメソッド化する

CSV読み込み処理は、メソッドに分けると再利用しやすくなります。

C#
using System;
using System.Collections.Generic;
using System.IO;

public class Person
{
public int Id { get; set; }
public string Name { get; set; } = "";
public int Age { get; set; }
}

class Program
{
static void Main()
{
List<Person> people = LoadPeopleFromCsv("people.csv");

foreach (Person person in people)
{
Console.WriteLine($"{person.Id}: {person.Name} ({person.Age}歳)");
}
}

static List<Person> LoadPeopleFromCsv(string filePath)
{
List<Person> people = new List<Person>();

using StreamReader reader = new StreamReader(filePath);

// ヘッダーをスキップ
reader.ReadLine();

string? line;
while ((line = reader.ReadLine()) != null)
{
if (string.IsNullOrWhiteSpace(line))
{
continue;
}

string[] columns = line.Split(',');

if (columns.Length < 3)
{
continue;
}

if (!int.TryParse(columns[0], out int id))
{
continue;
}

if (!int.TryParse(columns[2], out int age))
{
continue;
}

people.Add(new Person
{
Id = id,
Name = columns[1],
Age = age
});
}

return people;
}
}

メソッド化すると、Mainメソッドがすっきりし、CSV読み込み処理だけを別の場所から再利用できます。

7-2. 例外処理を追加して安全に読み込む

実務では、ファイルが存在しない、アクセス権がない、CSVの形式が不正など、さまざまなエラーが発生する可能性があります。

そのため、try-catchを使って例外処理を追加すると安全です。

C#
static List<Person> LoadPeopleFromCsv(string filePath)
{
List<Person> people = new List<Person>();

try
{
if (!File.Exists(filePath))
{
Console.WriteLine("CSVファイルが存在しません。");
return people;
}

using StreamReader reader = new StreamReader(filePath);

reader.ReadLine();

string? line;
while ((line = reader.ReadLine()) != null)
{
if (string.IsNullOrWhiteSpace(line))
{
continue;
}

string[] columns = line.Split(',');

if (columns.Length < 3)
{
continue;
}

if (!int.TryParse(columns[0], out int id))
{
continue;
}

if (!int.TryParse(columns[2], out int age))
{
continue;
}

people.Add(new Person
{
Id = id,
Name = columns[1],
Age = age
});
}
}
catch (IOException ex)
{
Console.WriteLine("ファイルの読み込み中にエラーが発生しました。");
Console.WriteLine(ex.Message);
}
catch (Exception ex)
{
Console.WriteLine("予期しないエラーが発生しました。");
Console.WriteLine(ex.Message);
}

return people;
}

例外処理を入れておくことで、エラーが発生してもプログラム全体が突然終了するのを防げます。

7-3. usingを使ってファイルを確実に閉じる

CSVファイルを読み込むときは、ファイルを開いたあとに必ず閉じる必要があります。

C#では、usingを使うことで、処理が終わったタイミングで自動的にリソースを解放できます。

C#
using StreamReader reader = new StreamReader(filePath);

または、次のようにブロック形式で書くこともできます。

C#
using (StreamReader reader = new StreamReader(filePath))
{
string? line;
while ((line = reader.ReadLine()) != null)
{
Console.WriteLine(line);
}
}

usingを使わない場合、ファイルが開いたままになり、次のような問題が起きる可能性があります。

ファイルを削除できない、別の処理で同じファイルを開けない、リソースが解放されない、といった問題です。

CSV読み込みでは、StreamReaderCsvReaderを使うときにusingを使う習慣を付けましょう。

7-4. 大きなCSVファイルを扱うときの注意点

小さなCSVであれば、すべての行をListに読み込んでも大きな問題はありません。

しかし、数十万行や数百万行の大きなCSVを扱う場合、すべてをListに入れるとメモリを大量に消費する可能性があります。

C#
List<string[]> rows = new List<string[]>();

このように全件をListに保持すると、CSVのサイズが大きいほどメモリ使用量も増えます。

大きなCSVを扱う場合は、1行ずつ読み込み、その場で処理する方法も検討します。

C#
using StreamReader reader = new StreamReader("large.csv");

string? line;
while ((line = reader.ReadLine()) != null)
{
string[] columns = line.Split(',');

// Listに追加せず、その場で処理する
Console.WriteLine(columns[0]);
}

CsvHelperを使う場合も、ToList()を呼ぶと全件がListに読み込まれます。

C#
List<Person> people = csv.GetRecords<Person>().ToList();

大きなCSVでは、次のように1件ずつ処理できます。

C#
foreach (Person person in csv.GetRecords<Person>())
{
Console.WriteLine(person.Name);
}

全件を保持する必要がない場合は、Listに入れずに逐次処理することで、メモリ使用量を抑えられます。

8. C#でCSVをListに読み込むときのよくある質問

8-1. List<string[]>とList<List<string>>はどちらを使うべきか

列数が固定されているCSVなら、List<string[]>がシンプルで扱いやすいです。

C#
List<string[]> rows = new List<string[]>();

各行の列数が決まっている場合、配列で十分です。

一方、列数が変わる可能性がある場合や、行ごとに列を追加・削除したい場合は、List<List<string>>の方が柔軟です。

C#
List<List<string>> rows = new List<List<string>>();

ただし、実務でCSVの列の意味が決まっている場合は、最終的にList<Person>List<Product>のようなクラスのListに変換する方法がおすすめです。

C#
List<Person> people = new List<Person>();

初心者が最初に学ぶならList<string[]>、実務で保守しやすいコードを書くならList<クラス名>を使うとよいでしょう。

8-2. CSVの1列だけをListに読み込めるか

CSVの1列だけをListに読み込むこともできます。

たとえば、次のCSVから名前だけをList<string>に読み込むとします。

csv
Id,Name,Age
1,田中,25
2,佐藤,30
3,鈴木,28

名前は2列目なので、インデックスは1です。

C#
using System;
using System.Collections.Generic;
using System.IO;

class Program
{
static void Main()
{
string filePath = "people.csv";
List<string> names = new List<string>();

using StreamReader reader = new StreamReader(filePath);

// ヘッダーをスキップ
reader.ReadLine();

string? line;
while ((line = reader.ReadLine()) != null)
{
string[] columns = line.Split(',');

if (columns.Length > 1)
{
names.Add(columns[1]);
}
}

foreach (string name in names)
{
Console.WriteLine(name);
}
}
}

実行結果は次のようになります。

田中
佐藤
鈴木

特定の列だけが必要な場合は、List<string>List<int>などに直接読み込むこともできます。

8-3. Excelで作成したCSVも読み込めるか

Excelで作成したCSVも、C#で読み込めます。

ただし、注意点があります。

Excelで保存したCSVは、文字コードがShift-JISになっている場合があります。その場合、UTF-8として読み込むと日本語が文字化けすることがあります。

文字化けする場合は、Shift-JISを指定して読み込みます。

C#
using System.Text;

Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);

using StreamReader reader = new StreamReader("people.csv", Encoding.GetEncoding("shift_jis"));

また、Excelで作成したCSVには、値がダブルクォーテーションで囲まれていたり、値の中にカンマが含まれていたりすることがあります。

csv
1,"田中,太郎",25

このようなCSVを扱う場合、Split(',')では正しく読み込めないため、CsvHelperを使うのがおすすめです。

8-4. カンマ以外の区切り文字にも対応できるか

カンマ以外の区切り文字にも対応できます。

たとえば、タブ区切りのファイルであれば、Split('\t')を使います。

C#
string[] columns = line.Split('\t');

セミコロン区切りであれば、次のように書きます。

C#
string[] columns = line.Split(';');

CsvHelperを使う場合は、区切り文字を設定できます。

C#
using System.Globalization;
using CsvHelper;
using CsvHelper.Configuration;

var config = new CsvConfiguration(CultureInfo.InvariantCulture)
{
Delimiter = "\t"
};

using StreamReader reader = new StreamReader("data.tsv");
using CsvReader csv = new CsvReader(reader, config);

List<Person> people = csv.GetRecords<Person>().ToList();

タブ区切りのファイルはCSVではなくTSVと呼ばれることもありますが、C#での読み込み方はCSVと似ています。

8-5. CSVを読み込んだあと検索や並び替えはできるか

CSVをListに読み込んだあとは、検索や並び替えができます。

List<Person>に変換している場合、LINQを使うと簡単です。

C#
using System.Linq;

年齢が30歳以上の人だけを取得する例です。

C#
List<Person> result = people.Where(p => p.Age >= 30).ToList();

名前が「田中」のデータを検索する例です。

C#
Person? tanaka = people.FirstOrDefault(p => p.Name == "田中");

年齢の昇順で並び替える例です。

C#
List<Person> sorted = people.OrderBy(p => p.Age).ToList();

年齢の降順で並び替える場合は、OrderByDescendingを使います。

C#
List<Person> sortedDesc = people.OrderByDescending(p => p.Age).ToList();

List<string[]>でも検索や並び替えはできますが、列番号で指定する必要があります。

C#
var result = rows.Where(r => r[1] == "田中").ToList();

クラスのListにしておくと、p.Namep.Ageのように意味のある名前で処理できるため、コードが読みやすくなります。

まとめ

C#でCSVをListに読み込む方法には、いくつかの選択肢があります。

最もシンプルなのは、StreamReaderでCSVを1行ずつ読み込み、Split(',')で分割してList<string[]>に格納する方法です。

C#
List<string[]> rows = new List<string[]>();

using StreamReader reader = new StreamReader("people.csv");

string? line;
while ((line = reader.ReadLine()) != null)
{
string[] columns = line.Split(',');
rows.Add(columns);
}

ヘッダー付きCSVでは、1行目をスキップしたり、ヘッダー行とデータ行を分けて扱ったりできます。

実務では、CSVの各行をクラスに変換してList<Person>のように扱うと、コードが読みやすくなります。

C#
public class Person
{
public int Id { get; set; }
public string Name { get; set; } = "";
public int Age { get; set; }
}

さらに、カンマを含む文字列やダブルクォーテーション付きのCSVを正しく扱いたい場合は、CsvHelperを使うのがおすすめです。

C#
using CsvHelper;
using System.Globalization;

using StreamReader reader = new StreamReader("people.csv");
using CsvReader csv = new CsvReader(reader, CultureInfo.InvariantCulture);

List<Person> people = csv.GetRecords<Person>().ToList();

初心者はまず、StreamReaderReadLine()Split(',')List<T>の基本を理解しましょう。そのうえで、ヘッダー付きCSV、型変換、例外処理、CsvHelperを使った読み込みを覚えると、C#でCSVをListに読み込む処理を実務でも使いやすくなります。