CsvReader必须配合StreamReader而非文件路径,需指定UTF8编码和InvariantCulture以防乱码与格式错误;大文件应避免GetRecords而改用Read+GetField流式处理。

用 CsvHelper 读 CSV 文件时,CsvReader 必须配 StreamReader,不能直接传文件路径
常见错误是写成 new CsvReader("data.csv"),编译就报错——CsvReader 构造函数不接受字符串路径。它要的是实现了 TextReader 的对象,比如 StreamReader。
正确做法是手动打开流,并注意编码(尤其含中文时):
using (var reader = new StreamReader("data.csv", Encoding.UTF8))
using (var csv = new CsvReader(reader, CultureInfo.InvariantCulture))
{
var records = csv.GetRecords<MyModel>();
}- 漏掉
Encoding.UTF8会导致中文乱码,Windows 默认 ANSI 编码读 UTF-8 文件必出问题 -
CultureInfo.InvariantCulture避免小数点、日期格式被本地化干扰(比如德国系统用逗号当小数点) - 必须用
using包裹,否则流不释放,下次读同一文件会提示“正由另一进程使用”
写 CSV 时 CsvWriter 要配合 StreamWriter,且记得调用 WriteRecords() 或循环 WriteRecord()
和读取一样,CsvWriter 不接收文件路径,也不自动换行或写 BOM。写完不 Flush() 或没用 using,文件可能为空或截断。
典型写法:
using (var writer = new StreamWriter("output.csv", false, Encoding.UTF8))
using (var csv = new CsvWriter(writer, CultureInfo.InvariantCulture))
{
csv.WriteRecords(myList); // 一次性写全部
// 或者逐条写:csv.WriteRecord(item);
}-
StreamWriter第二个参数false表示不追加(覆盖写),误写成true会导致重复数据 - 如果模型字段含换行符或逗号,CsvHelper 默认用双引号包裹,无需额外处理;但别手动拼接 CSV 字符串,容易漏转义
- 写入空集合时
WriteRecords()不会生成表头——需要表头就得先调WriteHeader<t>()</t>
映射类字段和 CSV 列名不一致?用 [Name] 或 [Index] 特性显式声明
CSV 列名是 "User ID",而 C# 属性叫 UserId,默认会匹配失败,GetRecords() 返回的字段全为默认值(0、null)。
两种可控方式:
public class MyModel
{
[Name("User ID")] // 按列名匹配
public int UserId { get; set; }
<pre class='brush:php;toolbar:false;'>[Index(2)] // 按第3列(从0开始)匹配
public string Email { get; set; }}
-
[Name]更直观,但大小写敏感;列名带空格、括号时必须用它 -
[Index]在列顺序固定但标题可能变时更稳,比如导出报表无标题行 - 如果完全不想依赖属性名,可改用
csv.GetRecords<idictionary object>>()</idictionary>手动取值
大文件(10MB+)用 GetRecords() 容易 OOM,该用 Read() + GetField() 流式解析
GetRecords<t>()</t> 会把整张表映射成内存对象列表,10 万行用户数据轻松吃掉 500MB+ 内存。不是所有场景都需要一次性加载。
低内存替代方案:
using (var reader = new StreamReader("big.csv"))
using (var csv = new CsvReader(reader, CultureInfo.InvariantCulture))
{
while (csv.Read())
{
int id = csv.GetField<int>(0);
string name = csv.GetField<string>(1);
// 处理单行,不累积对象
}
}-
csv.Read()每次只推进一行,配合GetField<t>(index)</t>直接取原始字段,跳过反射和对象构造开销 - 注意:字段类型转换失败会抛
CsvHelperException,需捕获并处理脏数据(如空字符串转 int) - 这种写法无法跳过表头,得手动
csv.Read()一次再进循环,或者用csv.ReadHeader()
字段命名不规范、数据质量差、文件超大——这三个问题撞上任意一个,就别图省事用 GetRecords。CsvHelper 很灵活,但默认行为是为“干净小表”设计的。


















