最稳妥方式是手动拼接并严格遵循rfc 4180规范:空值转空字符串,含逗号、换行、双引号或首尾空格时用双引号包裹,双引号转义为两个双引号;列名和数据均需转义;datetime用tostring("s"),decimal用tostring("g", invariantculture),布尔值显式转换;编码用utf-8无bom,行尾用\r\n。

DataTable 导出为 CSV 最稳妥的方式是手动拼接,别依赖 DataTable.WriteXml 或第三方库的“一键导出”——它们在处理换行、逗号、引号、空值时大概率翻车。
为什么不能直接用 ToString() 或 string.Join 拼接列名和行?
因为 CSV 规范要求:字段含逗号、换行符(\n)、双引号或开头/结尾为空格时,必须用双引号包裹;若字段本身含双引号,需转义为两个双引号("")。直接拼接会生成非法 CSV,Excel 打开后错列、丢数据、报错“文件格式不正确”。
常见错误现象:
- 某列含
"Name: \"John\"\nAge: 25"→ Excel 拆成两行、三列 - 某列值为
12,345→ 被当成两个字段 - 空字符串
""或null未处理 → 写入,,后续列整体右移
怎么安全地把每行 DataRow 转成合规 CSV 字段?
核心是写一个字段转义函数,严格遵循 RFC 4180。不需要引入 Microsoft.VisualBasic.FileIO.TextFieldParser,纯 C# 十几行搞定:
static string EscapeCsvField(string field)
{
if (field == null) return "";
if (!field.Contains(',') && !field.Contains('\n') && !field.Contains('"') && !field.Trim().StartsWith(" ") && !field.Trim().EndsWith(" "))
return field;
return $"\"{field.Replace("\"", "\"\"")}\"";
}
使用场景:
使用 qbo-mileage CLI 及用户凭证,从 Airtable、Outlook 或 Google Calendar 记录生成 QuickBooks Online 里程 CSV 文件。
- 字段含中文、emoji、制表符?没问题,只要没双引号就不用包引号
- 字段是
"A""B"?自动转成"A""""B" - 字段是
" leading"?因首空格被强制加引号,保留格式
导出时该用 StreamWriter 还是 StringBuilder?
看数据量。万行以内用 StringBuilder 内存拼接更快;超 5 万行建议用 StreamWriter 流式写入,避免 OutOfMemoryException。
关键细节:
- 必须指定编码:用
new StreamWriter(path, false, Encoding.UTF8),否则中文在 Excel(Windows)里乱码 - 首行写列名时,也要过
EscapeCsvField,因为列名可能含空格或括号,如"User ID" - 不要写 BOM 头(除非明确要求),Excel 识别 UTF-8 无 BOM 更稳定
- 每行末尾用
\r\n(Windows 标准),别用\n,否则某些工具显示为单行
遇到 DateTime 或 decimal 列导出格式不对怎么办?
DataRow[i] 是 object,直接 .ToString() 依赖当前线程文化,比如 decimal 在德语系统输出 123,45(逗号作小数点),CSV 解析器会误判为两字段。
解决方式:
- 对
DateTime:统一用dt.ToString("s")(ISO 8601,如2023-10-05T14:30:00)或自定义格式ToString("yyyy-MM-dd HH:mm:ss") - 对
decimal/double:用d.ToString("G", CultureInfo.InvariantCulture),确保小数点是英文句点 - 对布尔值:显式转
b ? "true" : "false",别依赖默认ToString()
最易被忽略的是文化相关转换——同一份代码,在开发机(中文 Windows)和服务器(英文 Linux)上导出的 CSV 可能结构不一致。务必显式控制格式化逻辑。










