用 TextFieldParser
读取 CSV 是最稳妥的内置方案
Windows Forms 或 .NET Framework 项目中,
Microsoft.VisualBasic.FileIO.TextFieldParser是唯一能正确处理带引号、换行符、转义逗号等复杂 CSV 场景的原生类。它不依赖第三方包,且对编码(如 UTF-8 BOM)、字段截断、空行有明确行为。
常见错误是直接用
string.Split(',') —— 遇到 "Smith, John",42,"123 Main St\nApt 4"这类数据会彻底解析错。 必须指定
TextFieldType = FieldType.Delimited并调用
SetDelimiters(",")
务必启用 HasFieldsEnclosedInQuotes = true,否则引号内逗号无法识别为分隔符的一部分 用
ReadFields()获取字符串数组,不要用
ReadLine()后再手动切分 注意:该类位于
Microsoft.VisualBasic.dll,需添加引用(即使项目是 C#)
using Microsoft.VisualBasic.FileIO;
// ...
using (var parser = new TextFieldParser("data.csv"))
{
parser.TextFieldType = FieldType.Delimited;
parser.SetDelimiters(",");
parser.HasFieldsEnclosedInQuotes = true;
parser.TrimWhiteSpace = true;
<pre class='brush:php;toolbar:false;'>while (!parser.EndOfData)
{
string[] fields = parser.ReadFields(); // 自动处理引号、换行、转义
Console.WriteLine(string.Join("|", fields));
}}
在 .NET Core / .NET 5+ 中优先选 System.Text.Json
+ 自定义 CSV 解析器
.NET Core 起不再默认包含
TextFieldParser(虽可通过 NuGet 引入
Microsoft.VisualBasic.Core),但更推荐轻量可控的自定义逻辑——尤其当 CSV 结构固定、无嵌套换行时。
关键点不是“能不能读”,而是“怎么避免分配爆炸”和“如何应对空值/类型转换”。比如逐行
Split后对每个字段调用
int.TryParse比全加载进
List<string></string>再批量转换更省内存。 用
StreamReader逐行读取,避免一次性
File.ReadAllLines占满内存 字段去引号逻辑必须手动实现:
field.StartsWith("\"") && field.EndsWith("\"") ? field.Substring(1, field.Length-2) : field
若需强类型映射(如转成 Person对象),建议配合
Span<char></char>做零分配解析,而非正则或 LINQ 警惕 BOM:UTF-8 文件开头可能有
\xEF\xBB\xBF,
StreamReader默认能识别,但自行
FileStream.Read时要跳过
Microsoft.Data.Analysis.DataFrame
适合分析型场景,但别用于通用解析
如果你的目标是做数据清洗、统计或对接 ML.NET,
Microsoft.Data.Analysis提供了类似 pandas 的
DataFrame和内置 CSV 加载器。但它会把整张表加载进内存,且列类型推断不可控(比如把全数字 ID 列误判为
int,遇到空值就崩)。
典型报错:
System.InvalidOperationException: Unable to convert column 'id' to type Int32 because of invalid value '' at row 123。 仅在明确需要后续调用
df.Filter、
df.GroupBy等操作时才引入 必须传入
new DataFrameLoadOptions { HasHeader = true, InferTypes = false } 关闭自动类型推断
列名含空格或特殊字符时,df["User Name"]可用,但
df.User Name会编译失败 不支持流式读取,无法处理 GB 级 CSV
第三方库 CsvHelper
的坑比功能还多
CsvHelper功能强大,但默认行为极易引发静默错误:比如忽略首行标题却不报错、字段数不匹配时悄悄丢弃末尾列、空字符串转
DateTime直接抛异常而非返回
null。
真实项目里,90% 的 “CsvHelper 读不出来” 问题都源于没配
CsvConfiguration。 必须显式设置
Configuration.Delimiter = ","和
Configuration.Quote = '"',不能依赖默认 映射类属性名与 CSV 列名不一致?得用
[Name("user_id")] 或 Map(m => m.UserId).Name("user_id")
空值处理:对可空类型(int?,
DateTime?)它能自动跳过,但对
string字段,空字符串不会被转成
null,得靠
ConvertUsing手动干预 性能敏感场景慎用:反射绑定 + 大量字符串分配,比手写
Span<char></char>解析慢 3–5 倍
真正难的从来不是“怎么读”,而是“怎么确保第 100001 行的引号没漏掉、空值没崩、编码没乱、内存没爆”。选方案前,先看你的 CSV 里有没有
"O'Reilly, Inc.", "C# \"Best Practices\"", 2024-04-01这种数据 —— 有,就老实用
TextFieldParser;没有,手写几行
StreamReader+
Span更省心。
