推荐使用 CsvHelper 库写入 CSV 文件,因其自动处理字段转义、编码、BOM、类型映射等问题,避免手动拼接导致的错列、丢行等解析错误。

直接写入 CSV 文件不需要第三方库,但手动拼接字符串极易出错——尤其当字段含逗号、换行符或双引号时。用 TextFieldParser(读)和手写(写)不匹配,推荐用 StreamWriter + 正确转义,或更稳妥地用 System.Text.Json 配合自定义序列化逻辑,但最省心的是引入 Polly?不,是 CsvHelper。
字段含逗号、引号、换行时,为什么直接 string.Join(",") 会崩
CSV 规范要求:字段若含 ,、" 或 \n,必须用双引号包裹,且内部的双引号要变成两个双引号(即 " → "")。手写逻辑漏掉任一情况,Excel 打开就错列、丢行。
- 错误示例:
"Alice, Dev","25"→ Excel 认为这是 3 列:"Alice、Dev"、"25" - 正确应为:
"Alice, Dev","25"(外层包引号,内部逗号不逃逸) - 更糟的是:
"Name: ""John""\nLocation: NY"→ 必须转成"Name: """"John"""",Location: NY",再换行 - 别自己写
EscapeCsvField()—— 边界多、测试难、后续维护人想打你
用 CsvHelper 写入对象列表,三步搞定
它自动处理转义、编码、BOM、类型映射,比手写健壮得多。安装 NuGet 包:CsvHelper(注意不是 CsvHelper.Core)。
- 定义类时用
[CsvHelper.Configuration.Attributes.Name("Full Name")]控制列名 - 写入前创建
CsvWriter,传入StreamWriter(建议指定Encoding.UTF8,避免中文乱码) - 调用
WriteRecords(list)即可;单条用WriteRecord(obj) - 若需跳过某属性,加
[Ignore];若字段名与属性名不一致,用[Name("user_id")]
using (var writer = new StreamWriter("users.csv", false, Encoding.UTF8))
using (var csv = new CsvWriter(writer, CultureInfo.InvariantCulture))
{
csv.WriteRecords(users); // users 是 List<user>
}</user>
不用第三方库?至少用 StreamWriter + Microsoft.VisualBasic.FileIO.TextFieldParser 的反向逻辑不行
TextFieldParser 是只读的,没有对应写入器。硬要裸写,必须实现 RFC 4180 兼容的转义 —— 但没人这么干。真要零依赖,可用以下最小安全写法:
- 对每个字段调用:
field.Contains(",") || field.Contains("\"") || field.Contains("\n") ? $"\"{field.Replace("\"", "\"\"")}\"" : field - 每行用
string.Join(",", escapedFields) + "\n" - 务必用
File.AppendAllText(path, line, Encoding.UTF8),而非File.WriteAllText(否则反复覆盖) - 注意:空字段也要保留,比如
,"",,不能省略
常见坑:BOM、编码、Excel 打不开、时间格式错乱
Windows 上 Excel 默认用 ANSI(其实是系统本地编码),UTF-8 无 BOM 时可能显示乱码;加 BOM 又可能被其他程序误读。解决方案:
- 写入时用
new UTF8Encoding(true)(true表示写 BOM),适合 Excel 主要场景 - 日期字段默认输出为
ToString(),可能带时区或本地格式;建议显式调用date.ToString("yyyy-MM-dd HH:mm:ss") - 数值字段如
double输出为123.45,但某些区域设置下 Excel 期待123,45(逗号作小数点)——此时应固定用culture: CultureInfo.InvariantCulture - 别用
DataTable.WriteXml()然后改后缀名,那不是 CSV
真正麻烦的从来不是“怎么写进去”,而是“怎么让 Excel、Power BI、Python pandas 都按你预期解析”。只要字段内容不可控,就别绕开 CsvHelper。它不重,API 直白,出问题能快速定位到配置层,而不是在字符串拼接里找少了一个引号。











