はじめに 「CSVエスケープ」は“カンマや改行を、1セルの中に閉じ込める”技
業務で CSV を扱うとき、いちばんよくハマるのが、
「値の中にカンマや改行が入っていて、列がズレる」
という問題です。
例えば、本当は「1列目:ID」「2列目:名前」「3列目:メモ」のつもりなのに、
メモの中にカンマが入っているせいで、Excel で開くと列が増えてしまう——これが典型的な事故です。
CSVエスケープは、一言でいうと、
「カンマや改行を含む文字列を、“1セル分の値”として正しくCSVに書き出すためのルールに従って変換すること」
です。
ここでは、初心者向けに、
- CSVの基本ルール(どきどきするほどシンプルだけど、守らないと壊れる)
- どんなときにエスケープが必要になるのか
- C#でのシンプルなCSVエスケープ実装
- 実務ユーティリティとしてのまとめ方
を、例題付きでかみ砕いて説明していきます。
CSVのルールをざっくり言葉でつかむ
「カンマ区切り」だけじゃない、もう一つの大事なルール
CSV(Comma-Separated Values)は、その名の通り「カンマで区切る」形式です。
ただし、もう一つ重要なルールがあります。
「値の中にカンマ・ダブルクォート・改行が含まれる場合、その値はダブルクォートで囲み、内部のダブルクォートは2つ重ねる」
これが CSV エスケープの本質です。
例を見たほうが早いので、いくつか挙げます。
元の値:山田太郎
→ そのまま:山田太郎
元の値:山田,太郎(カンマを含む)
→ "山田,太郎"
元の値:彼は「"天才"」と言われた(ダブルクォートを含む)
→ "彼は「""天才""」と言われた"
元の値:1行目\n2行目(改行を含む)
→ "1行目\n2行目"
つまり、
- カンマ・改行・ダブルクォートを含む値は「ダブルクォートで囲む」
- 中にあるダブルクォートは
""にする(2つ重ねる)
これを「CSVエスケープ」と呼んでいると思ってください。
どんなときにCSVエスケープが必要になるのか
「人間が自由に入力した文字列」はほぼ全部、要注意
業務システムで CSV を出力するとき、
次のような項目はほぼ確実にエスケープ対象になります。
- 備考・メモ・コメント
- 自由入力の名前・タイトル
- 住所(「○○, △△」のような表記が紛れ込むことがある)
- 改行を含む説明文
「この項目にはカンマなんて入らないだろう」と思っていても、
運用が続くと、誰かが入れてきます。
なので、「ルールに従って機械的にエスケープする」のが安全です。
C#でのシンプルなCSVエスケープ実装
1セル分の値をエスケープするメソッド
まずは、「1つの値(1セル分)をCSV用にエスケープする」ユーティリティを作ります。
public static class CsvEscapeUtil
{
public static string EscapeField(string? value)
{
if (value == null)
{
return string.Empty;
}
bool containsQuote = value.Contains('"');
bool containsComma = value.Contains(',');
bool containsNewLine = value.Contains('\n') || value.Contains('\r');
if (!containsQuote && !containsComma && !containsNewLine)
{
return value;
}
string escaped = value.Replace("\"", "\"\"");
return $"\"{escaped}\"";
}
}
C#やっていることを、順番に言葉で追ってみます。
- null は空文字として扱う(CSV的には空セル)。
- 値の中に「ダブルクォート」「カンマ」「改行」が含まれているかをチェックする。
- どれも含まれていなければ、そのまま返す(エスケープ不要)。
- 含まれている場合は、まずダブルクォート
"を""に置き換える。 - 最後に、全体をダブルクォートで囲む。
これで、CSVの基本ルールを満たしたエスケープができます。
動作例
Console.WriteLine(CsvEscapeUtil.EscapeField("山田太郎"));
// 山田太郎
Console.WriteLine(CsvEscapeUtil.EscapeField("山田,太郎"));
// "山田,太郎"
Console.WriteLine(CsvEscapeUtil.EscapeField("彼は\"天才\"と言われた"));
// "彼は""天才""と言われた"
Console.WriteLine(CsvEscapeUtil.EscapeField("1行目\n2行目"));
// "1行目
// 2行目"
C#この EscapeField を、1行分の各項目に対して呼び出し、
カンマで連結すれば、正しいCSVの1行が作れます。
1行分のCSVを組み立てるユーティリティ
配列やリストから1行の文字列を作る
次に、「複数の項目から1行のCSV行を作る」ユーティリティを用意します。
using System;
using System.Collections.Generic;
using System.Linq;
public static class CsvLineBuilder
{
public static string JoinFields(IEnumerable<string?> fields)
{
if (fields == null)
{
throw new ArgumentNullException(nameof(fields));
}
return string.Join(",", fields.Select(CsvEscapeUtil.EscapeField));
}
}
C#やっていることはシンプルです。
fieldsの各要素にEscapeFieldを適用する。- それらを
string.Join(",", ...)でカンマ連結する。
動作例
var fields = new[]
{
"1",
"山田,太郎",
"彼は\"天才\"と言われた",
"1行目\n2行目"
};
string line = CsvLineBuilder.JoinFields(fields);
Console.WriteLine(line);
C#出力イメージはこうなります。
1,"山田,太郎","彼は""天才""と言われた","1行目
2行目"
この1行をファイルに書き出していけば、
Excel や他システムで正しく読み取れる CSV が作れます。
実務での設計ポイントと注意点
「常にエスケープする」か「必要なときだけエスケープする」か
今回の実装では、
- カンマ・改行・ダブルクォートが含まれているときだけ、ダブルクォートで囲む
という「必要なときだけエスケープ」方式を採用しました。
一方で、「常にダブルクォートで囲む」という方針もあります。
例えば、こういう実装です。
public static string EscapeFieldAlwaysQuote(string? value)
{
if (value == null)
{
value = string.Empty;
}
string escaped = value.Replace("\"", "\"\"");
return $"\"{escaped}\"";
}
C#この方式だと、
- 実装がシンプル
- 「どの列も必ずダブルクォートで囲まれている」という前提で解析しやすい
というメリットがあります。
一方で、ファイルが少しだけ冗長になります(すべての値に " が付く)。
どちらを採用するかは、プロジェクトの方針や連携先の仕様に合わせて決めるとよいです。
改行コードの扱い
CSVの仕様上、「セルの中に改行があってもOK」です。
その場合は、今回のようにダブルクォートで囲んでおけば問題ありません。
ただし、実務では、
- 「1レコード=1行」として扱いたい
- セル内改行は許可しない(入力時に弾く)
という運用もよくあります。
もし「セル内改行は禁止」というルールにしたいなら、EscapeField の前に「改行をスペースに置き換える」「削除する」といった前処理を入れるのも一つの手です。
CSVエスケープユーティリティとしてまとめる
「1セル」「1行」「複数行」を段階的に分ける
設計としては、
- 1セル分のエスケープ:
EscapeField - 1行分の組み立て:
JoinFields - 複数行の出力:呼び出し側でループしながら
JoinFieldsを呼ぶ
という3段階に分けておくと、再利用性が高くなります。
例えば、こんな感じのユーティリティ構成にできます。
public static class CsvUtil
{
public static string EscapeField(string? value)
=> CsvEscapeUtil.EscapeField(value);
public static string JoinFields(IEnumerable<string?> fields)
=> CsvLineBuilder.JoinFields(fields);
}
C#呼び出し側は、
var line = CsvUtil.JoinFields(new[] { id, name, memo });
writer.WriteLine(line);
C#のように、「CSVのルール」を意識せずに使えるようになります。
まとめ 「CSVエスケープユーティリティ」は“カンマや改行を、壊さずにファイルへ流すためのフィルタ”
CSVエスケープは、地味ですが、
やっていないと「列ズレ」「読み込みエラー」「謎の改行」といったトラブルの原因になります。
押さえておきたいポイントは、
- CSVでは「カンマ・改行・ダブルクォートを含む値」はダブルクォートで囲む
- 中のダブルクォートは
""にする(2つ重ねる) - C#では「1セル分のエスケープ → カンマ連結で1行」をユーティリティ化するときれい
- 「必要なときだけ囲む」方式と「常に囲む」方式があり、方針を決めて統一する
- 改行やnullの扱いも、ユーティリティ側でルール化しておくと呼び出し側が楽になる
ここまで理解できれば、「なんとなく文字列を連結してCSVにしている」段階から一歩進んで、
“壊れないCSVを安定して吐き出せるC#ユーティリティ”を、自分の手で設計・実装できるようになっていきます。
