在C#开发中处理CSV文件时,字段包含逗号、引号等特殊字符是常见场景,按照CSV的标准规范,这类特殊字符需要通过特定的转义规则处理,否则直接按逗号分割字符串会导致数据解析错误。

CSV特殊字符的处理规则
CSV格式对特殊字符的处理有明确约定,核心规则如下:
- 如果字段内容包含逗号、换行符或者双引号,整个字段需要用双引号包裹
- 如果字段内容本身包含双引号,需要用两个连续的双引号表示一个双引号字符
- 包裹字段的双引号仅作为转义标识,不属于字段的实际内容
C#读取包含特殊字符的CSV
读取时需要按照上述规则逐字符解析,判断当前字符是否为引号、逗号或者换行符,处理转义逻辑。以下是完整的读取实现:
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
public class CsvReader
{
public static List<string[]> ReadCsv(string filePath, Encoding encoding = null)
{
encoding = encoding ?? Encoding.UTF8;
List<string[]> result = new List<string[]>();
using (StreamReader reader = new StreamReader(filePath, encoding))
{
string line;
while ((line = reader.ReadLine()) != null)
{
// 跳过空行
if (string.IsNullOrWhiteSpace(line))
{
continue;
}
result.Add(ParseCsvLine(line));
}
}
return result;
}
private static string[] ParseCsvLine(string line)
{
List<string> fields = new List<string>();
StringBuilder currentField = new StringBuilder();
bool inQuotes = false;
for (int i = 0; i < line.Length; i++)
{
char c = line[i];
if (inQuotes)
{
// 处于引号包裹的字段内
if (c == '"')
{
// 检查下一个字符是否也是引号,是的话表示一个双引号字符
if (i + 1 < line.Length && line[i + 1] == '"')
{
currentField.Append('"');
i++; // 跳过下一个引号
}
else
{
// 结束引号包裹状态
inQuotes = false;
}
}
else
{
currentField.Append(c);
}
}
else
{
// 未处于引号包裹状态
if (c == '"')
{
// 开始引号包裹状态
inQuotes = true;
}
else if (c == ',')
{
// 字段分隔符,保存当前字段
fields.Add(currentField.ToString());
currentField.Clear();
}
else
{
currentField.Append(c);
}
}
}
// 添加最后一个字段
fields.Add(currentField.ToString());
return fields.ToArray();
}
}
使用示例:假设存在CSV文件test.csv,内容如下:
姓名,描述,年龄 张三,"喜欢运动,爱吃苹果",20 李四,"他说:""今天天气真好""",25
调用读取方法后可以得到正确的字段数组:
var data = CsvReader.ReadCsv("test.csv");
foreach (var row in data)
{
Console.WriteLine(string.Join(" | ", row));
}
// 输出结果:
// 姓名 | 描述 | 年龄
// 张三 | 喜欢运动,爱吃苹果 | 20
// 李四 | 他说:"今天天气真好" | 25
C#写入包含特殊字符的CSV
写入时需要对字段内容进行检查,如果包含逗号、双引号或者换行符,就需要对字段进行转义处理,规则是包裹双引号,同时将字段内的双引号替换为两个双引号。以下是写入实现:
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
public class CsvWriter
{
public static void WriteCsv(string filePath, List<string[]> data, Encoding encoding = null)
{
encoding = encoding ?? Encoding.UTF8;
using (StreamWriter writer = new StreamWriter(filePath, false, encoding))
{
foreach (var row in data)
{
List<string> escapedFields = new List<string>();
foreach (var field in row)
{
escapedFields.Add(EscapeCsvField(field));
}
writer.WriteLine(string.Join(",", escapedFields));
}
}
}
private static string EscapeCsvField(string field)
{
if (string.IsNullOrEmpty(field))
{
return "";
}
// 检查是否包含需要转义的字符:逗号、双引号、换行符
if (field.Contains(",") || field.Contains(""") || field.Contains("n") || field.Contains("r"))
{
// 将字段内的双引号替换为两个双引号,然后包裹双引号
return """ + field.Replace(""", """") + """;
}
return field;
}
}
使用示例:写入包含特殊字符的数据到CSV文件:
List<string[]> writeData = new List<string[]>
{
new string[] { "姓名", "描述", "年龄" },
new string[] { "张三", "喜欢运动,爱吃苹果", "20" },
new string[] { "李四", "他说:"今天天气真好"", "25" }
};
CsvWriter.WriteCsv("output.csv", writeData);
生成的output.csv内容符合CSV规范,可以被其他支持标准CSV的工具正确解析。
注意事项
实际开发中还需要注意编码问题,读写CSV时尽量显式指定编码,避免默认编码导致中文乱码。如果处理的CSV文件很大,逐行读取的方式可以减少内存占用,不要一次性将整个文件加载到内存中。另外,部分CSV文件可能使用分号作为分隔符,处理前需要确认文件的实际分隔规则,再调整解析逻辑。