C Sharpの基礎 - CSVファイル
概要
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ファイルへの参照を追加する必要がある。
※注意
このクラスはVisual Basicのライブラリの一部であるため、C#で使用する場合は追加の依存関係が生じることに注意する。
RFC 4180との関係
TextFieldParserクラスは多くのRFC 4180の要件を満たしている。
ただし、いくつかの点で完全な準拠ではない。
- 準拠している点
- カンマで区切られたフィールドの処理
- 引用符で囲まれたフィールド内のカンマの処理
- 改行文字を含むフィールドの処理
- 部分的に準拠している点
- 引用符の扱い
TextFieldParser
クラスは引用符の使用を柔軟に設定できるが、デフォルトの動作がRFC 4180と完全に一致するわけではない。
- 引用符の扱い
- 準拠していない可能性がある点
- ヘッダ行の特別な扱い
- RFC 4180はヘッダ行について言及しているが、
TextFieldParser
クラスにはヘッダ行を自動的に識別する機能がない。
- RFC 4180はヘッダ行について言及しているが、
- 厳密なCRLF改行の要求
- RFC 4180はCRLF (\r\n) を改行として指定しているが、
TextFieldParser
クラスはより柔軟である。
- RFC 4180はCRLF (\r\n) を改行として指定しているが、
- ヘッダ行の特別な扱い
以下の例では、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);
}
}
}
}
}
以下の例では、上記の例を非同期で行っている。
非同期処理は、I/O操作が頻繁に行われる場合、または、ユーザインターフェースの応答性を維持する必要がある場合に特に有効である。
// 非同期処理
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);
}
}
}
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);
}
}
}