直接string.join会崩,因csv要求字段含逗号、换行或双引号时必须用双引号包裹且内部引号转义为两个;需用tocsvfield逐字段转义后再拼接,并以utf-8 bom写入防乱码。

字段含逗号、换行或双引号时,string.Join 直接拼接会崩
直接 string.Join(",", list) 看似省事,但只要任意字段含 ,、" 或 \r\n,Excel 打开就会错列、截断甚至乱码。这不是显示问题,是 CSV 格式本身被破坏了——CSV 不是“用逗号连起来就行”,它有明确的字段包裹和转义规则。
- 字段含逗号(如地址
"123 Main St, Springfield")必须整体用双引号包裹 - 字段含双引号(如
He said "Hi")必须把内部引号替换成两个引号:"He said ""Hi""" - 字段含换行符(如多行备注)也必须包裹,且换行符保留在引号内,不能当作行结束
ToCsvField 必须封装字段级转义逻辑,不能只做字符串替换
手写 .Replace("\"", "\"\"") 再加引号,看似可行,但漏掉空值、null、首尾空白等边界情况,容易在 Excel 里生成空列或解析失败。正确做法是写一个专注字段安全包装的函数:
Miller (mlr) 是一个命令行工具,用于查询、整形和重新格式化名称索引数据,如 CSV、TSV、JSON 和 JSON Lines。它将 awk、sed、cut、join 和 sort 的功能整合到一个专为结构化数据处理而构建的单一工具中。
static string ToCsvField(string s) =>
string.IsNullOrEmpty(s) ? "" : $"\"{s.Replace("\"", "\"\"")}\"";
- 这个函数只处理单个字段,不碰换行、不拆行、不拼接——职责单一
- 它不假设输入已 Trim,也不处理
\r\n转义(那是字段内容本身,保留即可) - 如果字段本身就是
"abc"(带引号),该函数会变成"""abc""",符合 RFC 4180:引号只在字段边界起作用,内部引号一律转义
导出 List<person></person> 时,别对对象直接 ToString() 拼接
常见错误是写 list.Select(x => x.Name + "," + x.Email),这等于把转义责任甩给开发者手动维护,一旦 Name 是 Smith, John 或 Email 是 user@domain.com(虽不含特殊字符,但未来可能变),就立刻失效。
- 先投影成字符串数组:
var rows = people.Select(p => new[] { p.Name, p.Email, p.Phone }); - 再逐行、逐字段调用
ToCsvField:rows.Select(row => string.Join(",", row.Select(ToCsvField))) - 最后用
Environment.NewLine连接所有行,不用\n或\r单独拼——Excel 在 Windows 下认\r\n,Environment.NewLine自动适配
UTF-8 文件必须带 BOM,否则 Excel 打开中文就是乱码
用 StreamWriter 写文件时,如果只传 Encoding.UTF8,生成的是无 BOM 的 UTF-8,Excel 默认按 ANSI 解析,中文全变问号或方块。必须显式启用 BOM:
using (var writer = new StreamWriter(path, false, new UTF8Encoding(true)))
-
new UTF8Encoding(true)中的true表示写入 BOM(字节序标记EF BB BF) - 不要用
Encoding.UTF8静态实例——它默认不带 BOM - BOM 只影响 Excel 等旧软件识别;现代编辑器(VS Code、Notepad++)能自动探测,但 Excel 不行










