C#常用类库-详解CsvHelper

C#常用类库-详解CsvHelper

在C#开发中,CSV(逗号分隔值)文件是数据交换的常用格式------批量导入数据、导出报表、对接第三方系统数据等场景,几乎离不开CSV的处理。原生处理CSV文件(如使用StreamReader逐行解析、手动拼接逗号分隔字符串),不仅代码繁琐、易出错,还需处理编码、格式异常、数据类型转换等诸多问题,开发效率低下且维护成本高。

CsvHelper作为.NET生态中最成熟、最高效的CSV处理类库,恰好解决了这一痛点。它基于.NET Standard开发,兼容所有.NET平台(.NET Framework 4.5+、.NET Core 2.0+、.NET 5/6/7/8、.NET MAUI等),封装了CSV的读取、写入、序列化/反序列化、格式配置等全套功能,一行代码即可完成复杂的CSV操作,大幅提升开发效率,同时支持高度自定义配置,适配各种复杂CSV场景。

本文从基础概念→核心用法→高级特性→企业级封装→性能优化→避坑指南,全方位、有深度地解析CsvHelper,结合实际开发场景(批量导入导出、数据校验、大文件处理),帮你彻底掌握这款CSV处理"神器",覆盖90%以上的企业级CSV处理需求。

一、前言:CsvHelper的定位与核心价值

在讲解CsvHelper之前,我们先明确两个核心问题:CsvHelper解决了什么痛点?它与原生处理方式、其他CSV类库(如FileHelpers)的差异是什么?这是理解CsvHelper设计理念、合理选型的基础。

1. 核心痛点:原生处理CSV的弊端

日常开发中,使用原生方式处理CSV文件,存在诸多繁琐且耗时的问题,尤其在复杂场景下更为突出:

  • 代码冗余且繁琐:读取CSV需逐行解析、分割字符串,处理表头与数据的对应关系;写入CSV需手动拼接逗号、处理换行符,代码重复率极高,无法复用。

  • 数据类型转换复杂:CSV文件中的数据均为字符串格式,需手动将其转换为int、DateTime、decimal等类型,易出现格式错误、空值异常。

  • 格式兼容性差:不同系统导出的CSV格式差异较大(如分隔符为分号、制表符,包含引号包裹、换行符转义),原生处理需手动适配,逻辑复杂。

  • 编码问题突出:CSV文件常见编码有UTF-8、GBK、GB2312等,原生读取/写入时若未正确设置编码,会出现中文乱码,排查难度大。

  • 大文件处理困难:直接读取大体积CSV文件(如100万行数据)会占用大量内存,易导致内存溢出,原生需手动实现流式处理,开发成本高。

  • 异常处理不完善:对格式错误(如列数不匹配、数据类型错误)的捕获和处理不够友好,需手动编写大量异常判断代码,易遗漏边缘场景。

而CsvHelper的出现,正是为了将开发者从这些繁琐的重复劳动中解放出来,专注于核心业务逻辑,同时提供更强大、更灵活的CSV处理能力,解决上述所有痛点。

2. CsvHelper的核心定位

CsvHelper是一款**.NET平台的高性能CSV处理类库**,核心目标是"简化CSV读写开发,提供企业级CSV处理能力"。它的设计理念是"约定优于配置",默认适配大多数CSV格式,同时支持高度自定义配置,既能满足简单的CSV读写需求,也能应对复杂的企业级场景(如大文件批量处理、自定义格式适配、数据校验)。

CsvHelper的核心优势的是"简洁易用、性能优异、灵活可定制",内置数据序列化/反序列化、表头映射、编码适配、异常处理等功能,无需额外依赖第三方类库,开箱即用,且社区活跃、更新迭代频繁,能及时修复问题、适配新的.NET版本。

3. CsvHelper vs 原生处理 vs FileHelpers(核心差异)

对比维度 CsvHelper 原生处理(StreamReader/StreamWriter) FileHelpers
开发效率 API简洁,一行代码完成读写,内置序列化,效率极高 需手动解析/拼接,代码繁琐,效率低下 支持强类型读写,但配置繁琐,灵活性不足
数据类型转换 自动转换,支持自定义类型转换器,可处理空值、异常格式 需手动转换,易出现类型异常,处理空值繁琐 支持自动转换,但自定义转换逻辑复杂
格式兼容性 支持自定义分隔符、引号、换行符,适配所有常见CSV格式 需手动适配不同格式,逻辑复杂,易出错 支持部分格式配置,但适配性不如CsvHelper全面
编码支持 完美支持UTF-8、GBK、GB2312等所有常见编码,配置简单 需手动设置编码,易出现乱码,排查困难 支持编码配置,但切换编码不够灵活
大文件处理 支持流式读写,内存占用低,可处理百万级数据 需手动实现流式处理,开发成本高,易出现内存溢出 支持大文件处理,但性能不如CsvHelper,配置复杂
可维护性 API统一,配置灵活,代码简洁,社区活跃,更新迭代频繁 代码分散,重复逻辑多,维护成本高,易出现bug 配置繁琐,API不够直观,维护难度中等
选型建议:
  • 简单CSV场景(如少量数据读写,格式固定),且不想引入第三方类库,可使用原生处理方式(适合入门级场景)。

  • 传统.NET Framework项目,且CSV格式简单、无需复杂配置,可选择FileHelpers(兼容性较好,但灵活性不足)。

  • 企业级开发、批量处理CSV、复杂格式适配、大文件处理、需要数据校验和自定义配置,优先选择CsvHelper,兼顾效率、灵活性与可维护性。

4. 版本与安装

CsvHelper最新稳定版本为30.0.1(本文基于此版本讲解,适配.NET 6+,向下兼容.NET Core 2.0+、.NET Framework 4.5+),安装方式极其简单,通过NuGet安装核心包即可,无需额外依赖:

  • 核心包(必装):Install-Package CsvHelper(.NET CLI:dotnet add package CsvHelper

  • 扩展包(按需):CsvHelper默认已包含所有核心功能,无需额外安装扩展包;若需与EntityFramework Core集成,可安装CsvHelper.EntityFrameworkCore

安装完成后,引入核心命名空间即可使用:using CsvHelper;using CsvHelper.Configuration;

注意:CsvHelper 20+版本与旧版本(如15.x)API差异较大,本文基于最新版本讲解,避免使用过时API(如旧版本的CsvReader.Read()方法已被废弃)。

二、基础用法:从零开始使用CsvHelper(核心场景)

CsvHelper的API设计极其简洁,核心流程分为3步:配置CSV格式→创建读写器实例→执行读写操作并处理数据。基础用法覆盖4个核心场景:CSV读取(简单读取、强类型读取)、CSV写入(简单写入、强类型写入),这4个场景几乎能满足80%的日常开发需求。

1. 核心基础:CsvConfiguration与核心对象

CsvHelper的所有操作,都围绕两个核心对象和一个配置类展开,理解它们的作用,是掌握CsvHelper的基础:

  • CsvConfiguration:CSV格式配置类,负责配置分隔符、编码、表头、引号规则、数据类型转换等,是CsvHelper灵活性的核心,可根据需求自定义配置。

  • CsvReader:CSV读取器,负责从流(Stream)、文件(FileStream)、字符串中读取CSV数据,支持逐行读取、批量读取、强类型反序列化。

  • CsvWriter:CSV写入器,负责将数据(字符串、实体类)写入流、文件或字符串,支持逐行写入、批量写入、强类型序列化。

基础配置示例(默认配置,适配标准CSV格式):

csharp 复制代码
using CsvHelper;
using CsvHelper.Configuration;
using System.Globalization;

// 1. 配置CSV格式(默认配置:逗号分隔、UTF-8编码、第一行为表头)
var config = new CsvConfiguration(CultureInfo.InvariantCulture)
{
    Encoding = System.Text.Encoding.UTF8, // 编码
    Delimiter = ",", // 分隔符
    HasHeaderRecord = true, // 是否有表头
    Quote = "\"", // 引号字符(用于包裹包含逗号、换行符的字段)
    AllowComments = false, // 是否允许注释(默认不允许)
    IgnoreBlankLines = true // 忽略空行
};

// 2. 创建CsvReader实例(从文件读取)
using (var reader = new StreamReader("data.csv"))
using (var csvReader = new CsvReader(reader, config))
{
    // 读取操作...
}

// 3. 创建CsvWriter实例(写入文件)
using (var writer = new StreamWriter("output.csv"))
using (var csvWriter = new CsvWriter(writer, config))
{
    // 写入操作...
}

注意:CsvReader和CsvWriter均实现了IDisposable接口,必须使用using语句包裹,确保资源正确释放,避免内存泄漏。

2. CSV读取(最常用场景)

CSV读取是日常开发中最常见的场景,分为"简单读取(无模型,适用于表头不固定)"和"强类型读取(有模型,适用于表头固定)"两种方式,CsvHelper均提供了简洁的API支持。

(1)简单读取(无模型,逐行读取)

适用于CSV表头不固定、数据结构简单的场景,读取后以字典(Dictionary)形式存储每行数据,键为表头名称,值为对应的数据(字符串格式)。

csharp 复制代码
using CsvHelper;
using CsvHelper.Configuration;
using System.Globalization;
using System.Text;

// 1. 配置CSV格式(适配中文编码,避免乱码)
var config = new CsvConfiguration(CultureInfo.InvariantCulture)
{
    Encoding = Encoding.UTF8,
    HasHeaderRecord = true,
    IgnoreBlankLines = true
};

// 2. 读取CSV文件(逐行读取)
var csvData = new List<Dictionary<string, string>>();
using (var reader = new StreamReader("data.csv"))
using (var csvReader = new CsvReader(reader, config))
{
    // 读取表头(可选,若需获取表头信息)
    csvReader.ReadHeader();
    var headers = csvReader.HeaderRecord; // 表头数组(如:["Id", "Name", "Age"])
    
    // 逐行读取数据,直到结束
    while (csvReader.Read())
    {
        // 方式1:通过表头名称获取对应字段值
        var row = new Dictionary<string, string>
        {
            { "Id", csvReader.GetField("Id") },
            { "Name", csvReader.GetField("Name") },
            { "Age", csvReader.GetField("Age") }
        };
        
        // 方式2:获取当前行所有字段(按表头顺序)
        // var fields = csvReader.GetRecord<dynamic>();
        
        csvData.Add(row);
    }
}

// 3. 处理读取到的数据
foreach (var row in csvData)
{
    Console.WriteLine($"Id: {row["Id"]}, Name: {row["Name"]}, Age: {row["Age"]}");
}
(2)强类型读取(有模型,推荐)

适用于CSV表头固定、数据结构清晰的场景,通过定义实体类,将CSV数据自动反序列化为实体对象,无需手动转换数据类型,效率更高、更易维护。

csharp 复制代码
using CsvHelper;
using CsvHelper.Configuration;
using System.Globalization;
using System.Text;

// 1. 定义实体类(与CSV表头对应)
public class User
{
    public int Id { get; set; } // 对应CSV表头"Id"
    public string Name { get; set; } // 对应CSV表头"Name"
    public int Age { get; set; } // 对应CSV表头"Age"
    public DateTime CreateTime { get; set; } // 对应CSV表头"CreateTime"
}

// 2. 配置CSV格式(处理日期格式)
var config = new CsvConfiguration(CultureInfo.InvariantCulture)
{
    Encoding = Encoding.UTF8,
    HasHeaderRecord = true,
    IgnoreBlankLines = true,
    // 配置日期格式(若CSV中日期格式为"yyyy-MM-dd HH:mm:ss")
    DateTimeFormat = "yyyy-MM-dd HH:mm:ss"
};

// 3. 强类型读取(自动反序列化)
var users = new List<User>();
using (var reader = new StreamReader("users.csv"))
using (var csvReader = new CsvReader(reader, config))
{
    // 方式1:批量读取所有数据(适用于小文件)
    users = csvReader.GetRecords<User>().ToList();
    
    // 方式2:逐行读取(适用于大文件,避免内存溢出)
    // while (csvReader.Read())
    // {
    //     var user = csvReader.GetRecord<User>();
    //     users.Add(user);
    // }
}

// 4. 处理读取到的实体数据
foreach (var user in users)
{
    Console.WriteLine($"Id: {user.Id}, Name: {user.Name}, Age: {user.Age}, CreateTime: {user.CreateTime:yyyy-MM-dd}");
}

注意:强类型读取时,实体类的属性名称需与CSV表头名称一致(大小写不敏感,可通过配置关闭大小写敏感);若名称不一致,可通过"映射配置"解决(后续高级特性中讲解)。

3. CSV写入(核心场景)

CSV写入用于将数据(实体类、字典、字符串)导出为CSV文件,分为"简单写入(无模型)"和"强类型写入(有模型)"两种方式,CsvHelper可自动处理数据类型转换、表头生成、格式适配。

(1)简单写入(无模型,逐行写入)

适用于数据结构不固定、无需实体类的场景,可手动写入表头和每行数据,灵活度高。

csharp 复制代码
using CsvHelper;
using CsvHelper.Configuration;
using System.Globalization;
using System.Text;

// 1. 配置CSV格式(写入为GBK编码,适配Windows默认CSV打开方式)
var config = new CsvConfiguration(CultureInfo.InvariantCulture)
{
    Encoding = Encoding.GetEncoding("GBK"),
    HasHeaderRecord = true,
    Delimiter = ","
};

// 2. 写入CSV文件(逐行写入)
using (var writer = new StreamWriter("output.csv"))
using (var csvWriter = new CsvWriter(writer, config))
{
    // 第一步:写入表头
    csvWriter.WriteField("Id");
    csvWriter.WriteField("Name");
    csvWriter.WriteField("Age");
    csvWriter.NextRecord(); // 换行,进入下一行
    
    // 第二步:逐行写入数据
    csvWriter.WriteField(1);
    csvWriter.WriteField("张三");
    csvWriter.WriteField(25);
    csvWriter.NextRecord();
    
    csvWriter.WriteField(2);
    csvWriter.WriteField("李四");
    csvWriter.WriteField(28);
    csvWriter.NextRecord();
    
    // 批量写入(通过数组)
    var row = new object[] { 3, "王五", 30 };
    csvWriter.WriteRecord(row);
    csvWriter.NextRecord();
}

Console.WriteLine("CSV写入完成");
(2)强类型写入(有模型,推荐)

适用于数据结构固定、有实体类的场景,可直接将实体集合写入CSV文件,自动生成表头、转换数据类型,简洁高效,是企业级开发的首选方式。

csharp 复制代码
using CsvHelper;
using CsvHelper.Configuration;
using System.Globalization;
using System.Text;

// 1. 复用User实体类(与读取时一致)
public class User
{
    public int Id { get; set; }
    public string Name { get; set; }
    public int Age { get; set; }
    public DateTime CreateTime { get; set; }
}

// 2. 准备要写入的数据(实体集合)
var users = new List<User>
{
    new User { Id = 1, Name = "张三", Age = 25, CreateTime = DateTime.Now.AddDays(-10) },
    new User { Id = 2, Name = "李四", Age = 28, CreateTime = DateTime.Now.AddDays(-5) },
    new User { Id = 3, Name = "王五", Age = 30, CreateTime = DateTime.Now }
};

// 3. 配置CSV格式(自定义日期格式、表头名称)
var config = new CsvConfiguration(CultureInfo.InvariantCulture)
{
    Encoding = Encoding.UTF8,
    HasHeaderRecord = true,
    DateTimeFormat = "yyyy-MM-dd", // 日期格式简化为年月日
    // 关闭大小写敏感(避免表头与实体属性大小写不一致导致的问题)
    IgnoreCase = true
};

// 4. 强类型写入(自动序列化)
using (var writer = new StreamWriter("users_output.csv"))
using (var csvWriter = new CsvWriter(writer, config))
{
    // 方式1:批量写入所有实体(适用于小数据量)
    csvWriter.WriteRecords(users);
    
    // 方式2:逐行写入(适用于大数据量,避免内存占用过高)
    // 先写入表头
    // csvWriter.WriteHeader<User>();
    // csvWriter.NextRecord();
    // // 逐行写入实体
    // foreach (var user in users)
    // {
    //     csvWriter.WriteRecord(user);
    //     csvWriter.NextRecord();
    // }
}

Console.WriteLine("强类型CSV写入完成");

注意:强类型写入时,WriteRecords(users)方法会自动写入表头(基于实体类的属性名称)和所有实体数据;若需自定义表头名称,可通过"映射配置"实现。

4. 基础配置拓展(常用场景)

CsvHelper的CsvConfiguration类提供了大量可配置项,适配不同场景的CSV格式,以下是日常开发中最常用的配置:

csharp 复制代码
var config = new CsvConfiguration(CultureInfo.InvariantCulture)
{
    // 编码配置(解决中文乱码)
    Encoding = Encoding.UTF8, // 常用编码:UTF8、GBK(Encoding.GetEncoding("GBK"))
    // 分隔符配置(适配非逗号分隔的CSV)
    Delimiter = ";", // 分号分隔(常见于欧洲地区CSV)
    // Delimiter = "\t", // 制表符分隔(TSV文件)
    // 表头配置
    HasHeaderRecord = true, // 是否有表头
    HeaderValidated = (args) => 
    {
        // 表头验证(如忽略重复表头)
        if (args.HeaderNames.Distinct().Count() != args.HeaderNames.Count())
        {
            args.IsValid = false;
        }
    },
    // 数据类型转换配置
    TypeConverterOptionsCache = new TypeConverterOptionsCache
    {
        // 配置decimal类型的格式(保留2位小数)
        { typeof(decimal), new TypeConverterOptions { NumberStyle = NumberStyles.Currency } }
    },
    DateTimeFormat = "yyyy-MM-dd HH:mm:ss", // 日期格式
    // 异常处理配置
    MissingFieldFound = null, // 忽略缺失的字段(避免因列数不匹配报错)
    BadDataFound = (args) =>
    {
        // 处理格式错误的数据(如引号未闭合)
        Console.WriteLine($"格式错误:{args.RawRecord}");
    },
    // 其他配置
    IgnoreBlankLines = true, // 忽略空行
    AllowComments = true, // 允许注释(注释行以#开头)
    Comment = '#' // 注释符号
};

三、核心特性:CsvHelper高级用法(深度重点)

基础用法能满足日常简单需求,而CsvHelper的高级特性则能适配复杂企业级场景(如自定义映射、数据校验、大文件处理、特殊字符处理),这也是它区别于其他CSV类库的核心优势。

1. 自定义映射(表头与实体不匹配场景必备)

实际开发中,CSV表头往往与实体类的属性名称不一致(如CSV表头为"用户ID",实体属性为"Id"),此时需要通过自定义映射,建立表头与实体属性的对应关系,避免反序列化失败。CsvHelper提供了两种映射方式:特性映射(简单场景)和ClassMap映射(复杂场景)。

(1)特性映射(简单场景,推荐)

通过在实体类的属性上添加[Name]特性,直接指定对应的CSV表头名称,简洁高效。

csharp 复制代码
using CsvHelper.Configuration.Attributes;

// 实体类添加特性,指定CSV表头名称
public class User
{
    [Name("用户ID")] // 对应CSV表头"用户ID"
    public int Id { get; set; }
    
    [Name("用户姓名")] // 对应CSV表头"用户姓名"
    public string Name { get; set; }
    
    [Name("年龄")] // 对应CSV表头"年龄"
    public int Age { get; set; }
    
    [Name("创建时间")] // 对应CSV表头"创建时间"
    [Format("yyyy-MM-dd HH:mm:ss")] // 指定日期格式
    public DateTime CreateTime { get; set; }
    
    [Ignore] // 忽略该属性(不读取、不写入)
    public string Remark { get; set; }
}

// 读取/写入时,无需额外配置,自动识别特性映射
using (var reader = new StreamReader("users.csv"))
using (var csvReader = new CsvReader(reader, config))
{
    var users = csvReader.GetRecords<User>().ToList(); // 自动匹配表头与属性
}
(2)ClassMap映射(复杂场景,推荐)

适用于映射逻辑复杂的场景(如多个CSV表头对应同一个实体属性、需要自定义类型转换、动态映射),通过继承ClassMap<T>类,手动配置映射关系,灵活性更高。

csharp 复制代码
using CsvHelper;
using CsvHelper.Configuration;
using CsvHelper.TypeConversion;

// 1. 定义映射类(继承ClassMap<User>)
public class UserMap : ClassMap<User>
{
    public UserMap()
    {
        // 1. 基础映射:表头与属性对应
        Map(m => m.Id).Name("用户ID"); // 表头"用户ID"对应属性Id
        Map(m => m.Name).Name("用户姓名").Alias("姓名"); // 支持多个表头别名(适配不同CSV)
        Map(m => m.Age).Name("年龄");
        
        // 2. 日期格式映射
        Map(m => m.CreateTime).Name("创建时间").TypeConverterOption.Format("yyyy-MM-dd HH:mm:ss");
        
        // 3. 忽略属性
        Map(m => m.Remark).Ignore();
        
        // 4. 自定义类型转换(如将CSV中的"男/女"转换为bool类型)
        Map(m => m.IsMale).Name("性别").TypeConverter(new GenderConverter());
        
        // 5. 映射顺序(控制写入CSV时的表头顺序)
        Map(m => m.Id).Index(0); // 第1列
        Map(m => m.Name).Index(1); // 第2列
        Map(m => m.Age).Index(2); // 第3列
    }
}

// 2. 自定义类型转换器(将"男"转为true,"女"转为false)
public class GenderConverter : DefaultTypeConverter
{
    public override object ConvertFromString(string text, IReaderRow row, MemberMapData memberMapData)
    {
        return text == "男" ? true : false;
    }

    public override string ConvertToString(object value, IWriterRow row, MemberMapData memberMapData)
    {
        return (bool)value ? "男" : "女";
    }
}

// 3. 使用映射类(读取/写入时注册)
var config = new CsvConfiguration(CultureInfo.InvariantCulture)
{
    Encoding = Encoding.UTF8,
    HasHeaderRecord = true
};

// 注册映射类(关键步骤)
config.RegisterClassMap<UserMap>();

// 读取数据(自动应用映射)
using (var reader = new StreamReader("users.csv"))
using (var csvReader = new CsvReader(reader, config))
{
    var users = csvReader.GetRecords<User>().ToList();
}

// 写入数据(自动应用映射,表头顺序与映射类一致)
using (var writer = new StreamWriter("users_output.csv"))
using (var csvWriter = new CsvWriter(writer, config))
{
    csvWriter.WriteRecords(users);
}

2. 数据校验(企业级场景必备)

批量导入CSV数据时,往往需要校验数据的合法性(如必填字段、数据类型、范围限制),避免无效数据进入系统。CsvHelper可结合数据校验逻辑,在读取数据时实时校验,及时捕获异常数据。

csharp 复制代码
using CsvHelper;
using CsvHelper.Configuration;
using System.Globalization;
using System.Text;

// 1. 定义实体类(添加校验规则提示)
public class User
{
    public int Id { get; set; } // 必填,非负整数
    public string Name { get; set; } // 必填,长度1-50
    public int Age { get; set; } // 必填,18-60
    public DateTime CreateTime { get; set; } // 必填,不晚于当前时间
}

// 2. 读取数据并校验
var validUsers = new List<User>();
var invalidRecords = new List<string>(); // 存储无效数据行

var config = new CsvConfiguration(CultureInfo.InvariantCulture)
{
    Encoding = Encoding.UTF8,
    HasHeaderRecord = true,
    MissingFieldFound = (args) =>
    {
        // 捕获缺失字段的异常,记录无效行
        invalidRecords.Add($"缺失字段:{args.RawRecord}");
    },
    BadDataFound = (args) =>
    {
        // 捕获格式错误的异常,记录无效行
        invalidRecords.Add($"格式错误:{args.RawRecord}");
    }
};

using (var reader = new StreamReader("users.csv"))
using (var csvReader = new CsvReader(reader, config))
{
    csvReader.ReadHeader(); // 读取表头
    while (csvReader.Read())
    {
        try
        {
            // 读取当前行数据
            var user = csvReader.GetRecord<User>();
            
            // 自定义校验逻辑
            var errors = new List<string>();
            if (user.Id <= 0) errors.Add("Id必须为正整数");
            if (string.IsNullOrWhiteSpace(user.Name) || user.Name.Length > 50) errors.Add("姓名必填,长度1-50");
            if (user.Age < 18 || user.Age > 60) errors.Add("年龄必须在18-60之间");
            if (user.CreateTime > DateTime.Now) errors.Add("创建时间不能晚于当前时间");
            
            // 校验通过,添加到有效列表;否则记录无效行
            if (errors.Count == 0)
            {
                validUsers.Add(user);
            }
            else
            {
                var errorMsg = $"数据无效:{csvReader.RawRecord},错误:{string.Join("、", errors)}";
                invalidRecords.Add(errorMsg);
            }
        }
        catch (Exception ex)
        {
            // 捕获其他异常(如类型转换失败)
            invalidRecords.Add($"读取失败:{csvReader.RawRecord},异常:{ex.Message}");
        }
    }
}

// 3. 处理校验结果
Console.WriteLine($"有效数据:{validUsers.Count} 条");
Console.WriteLine($"无效数据:{invalidRecords.Count} 条");
foreach (var error in invalidRecords)
{
    Console.WriteLine(error);
}

3. 大文件处理(避免内存溢出)

处理大体积CSV文件(如100万行、几十MB甚至几百MB)时,若一次性读取所有数据到内存,会导致内存溢出。CsvHelper支持流式读写,逐行读取/写入数据,内存占用始终保持在较低水平,完美适配大文件场景。

(1)大文件读取(流式读取)
csharp 复制代码
using CsvHelper;
using CsvHelper.Configuration;
using System.Globalization;
using System.Text;

var config = new CsvConfiguration(CultureInfo.InvariantCulture)
{
    Encoding = Encoding.UTF8,
    HasHeaderRecord = true,
    IgnoreBlankLines = true
};

// 流式读取:逐行读取,不一次性加载所有数据到内存
using (var reader = new StreamReader("large_users.csv")) // 大文件(如100万行)
using (var csvReader = new CsvReader(reader, config))
{
    csvReader.ReadHeader(); // 读取表头
    int count = 0;
    
    // 逐行读取,处理完一行释放一行内存
    while (csvReader.Read())
    {
        var user = csvReader.GetRecord<User>();
        
        // 处理当前用户数据(如写入数据库、批量处理)
        ProcessUser(user);
        
        count++;
        if (count % 10000 == 0)
        {
            Console.WriteLine($"已处理 {count} 条数据");
        }
    }
    
    Console.WriteLine($"处理完成,共处理 {count} 条数据");
}

// 模拟数据处理(如写入数据库)
private static void ProcessUser(User user)
{
    // 数据库插入逻辑...
}
(2)大文件写入(流式写入)

写入大体积数据时,逐行写入可避免一次性将所有数据加载到内存,降低内存占用。

csharp 复制代码
using CsvHelper;
using CsvHelper.Configuration;
using System.Globalization;
using System.Text;

var config = new CsvConfiguration(CultureInfo.InvariantCulture)
{
    Encoding = Encoding.UTF8,
    HasHeaderRecord = true
};

// 流式写入:逐行写入,不一次性加载所有数据到内存
using (var writer = new StreamWriter("large_output.csv"))
using (var csvWriter = new CsvWriter(writer, config))
{
    // 写入表头
    csvWriter.WriteHeader<User>();
    csvWriter.NextRecord();
    
    int count = 0;
    // 模拟100万条数据(从数据库分页查询,逐页写入)
    for (int page = 1; page <= 100; page++)
    {
        // 从数据库分页查询数据(每页10000条)
        var users = GetUsersFromDb(page, 10000);
        
        // 逐行写入当前页数据
        foreach (var user in users)
        {
            csvWriter.WriteRecord(user);
            csvWriter.NextRecord();
            count++;
        }
        
        Console.WriteLine($"已写入 {count} 条数据");
    }
    
    Console.WriteLine($"写入完成,共写入 {count} 条数据");
}

// 模拟从数据库分页查询数据
private static List<User> GetUsersFromDb(int page, int pageSize)
{
    // 数据库分页查询逻辑...
    return new List<User>();
}

4. 特殊字符与格式处理(避坑重点)

CSV文件中常见特殊场景(如字段包含逗号、引号、换行符,空值处理),若处理不当会导致CSV格式错乱、读取失败,CsvHelper提供了完善的解决方案。

csharp 复制代码
using CsvHelper;
using CsvHelper.Configuration;
using System.Globalization;
using System.Text;

// 配置特殊字符处理
var config = new CsvConfiguration(CultureInfo.InvariantCulture)
{
    Encoding = Encoding.UTF8,
    HasHeaderRecord = true,
    // 1. 处理字段包含逗号、引号、换行符(自动用引号包裹)
    ShouldQuote = (field, context) =>
    {
        // 当字段包含逗号、引号、换行符时,自动添加引号包裹
        return field.Contains(",") || field.Contains("\"") || field.Contains(Environment.NewLine);
    },
    // 2. 空值处理(将null/空字符串替换为指定内容)
    NullValue = "--", // 读取时,将"--"转为null;写入时,将null转为"--"
    // 3. 引号转义(当字段包含引号时,自动转义为双引号)
    QuoteEscape = "\"\"",
    // 4. 处理换行符(允许字段内包含换行符)
    AllowNewLineInField = true
};

// 写入包含特殊字符的数据
var users = new List<User>
{
    new User 
    { 
        Id = 1, 
        Name = "张三,李四", // 字段包含逗号
        Age = 25, 
        CreateTime = DateTime.Now,
        Remark = "这是一段包含\n换行符和\"引号\"的备注" // 包含换行符和引号
    }
};

using (var writer = new StreamWriter("special_chars.csv"))
using (var csvWriter = new CsvWriter(writer, config))
{
    csvWriter.WriteRecords(users);
}

// 读取包含特殊字符的CSV
using (var reader = new StreamReader("special_chars.csv"))
using (var csvReader = new CsvReader(reader, config))
{
    var user = csvReader.GetRecords<User>().FirstOrDefault();
    Console.WriteLine($"Name: {user.Name}"); // 输出:张三,李四
    Console.WriteLine($"Remark: {user.Remark}"); // 输出:这是一段包含换行符和"引号"的备注
}

5. 编码适配(解决中文乱码)

中文乱码是CSV处理中最常见的问题,核心原因是编码不匹配(如CSV文件为GBK编码,读取时用UTF-8编码)。CsvHelper可通过配置Encoding属性,完美适配所有常见编码。

csharp 复制代码
using CsvHelper;
using CsvHelper.Configuration;
using System.Globalization;
using System.Text;

// 场景1:读取GBK编码的CSV(Windows默认导出的CSV多为GBK)
var gbkConfig = new CsvConfiguration(CultureInfo.InvariantCulture)
{
    Encoding = Encoding.GetEncoding("GBK"), // 关键:设置为GBK编码
    HasHeaderRecord = true
};

using (var reader = new StreamReader("gbk_users.csv", Encoding.GetEncoding("GBK")))
using (var csvReader = new CsvReader(reader, gbkConfig))
{
    var users = csvReader.GetRecords<User>().ToList(); // 无乱码
}

// 场景2:写入GBK编码的CSV(适配Windows记事本打开)
using (var writer = new StreamWriter("gbk_output.csv", false, Encoding.GetEncoding("GBK")))
using (var csvWriter = new CsvWriter(writer, gbkConfig))
{
    csvWriter.WriteRecords(users);
}

// 场景3:UTF
相关推荐
军训猫猫头2 小时前
5.正弦波生成器:支持连续相位与可控重置 C# + WPF 完整示例
c#·.net·wpf
006_2 小时前
Java8的lambda用法总结
前端·数据库
倔强的石头1062 小时前
KWDB 3.1.0 制造业实战:从 0 到 1 搭建工业设备健康监测系统
数据库·kwdb
qq_4924484462 小时前
Maven直接下载jar包
数据库·maven·jar
刚入坑的新人编程2 小时前
C++qt(3)-按钮类控件
开发语言·c++·qt
开始了码2 小时前
基于 Qt 实现多客户端 TCP 通信聊天室
开发语言·数据库·php
肥猪猪爸2 小时前
数据库 2PC 极简流程图
java·数据库·分布式·mysql·分布式事务·2pc
一只空白格2 小时前
ThreadLocal的作用和底层原理
java·开发语言·jvm
dot to one2 小时前
B树系列在数据库中的应用
数据结构·数据库·b树