ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

CsvHelper 自定义类型转换器(Custom Type Converters)完整实战指南

CsvHelper 自定义类型转换器(Custom Type Converters)完整实战指南 后端数据工程【免费下载链接】CsvHelperLibrary to help reading and writing CSV files项目地址https://gitcode.com/gh_mirrors/cs/CsvHelper点击查看免费下载CsvHelper 内置的类型转换器已经覆盖了绝大多数 .NET 基础类型与集合类型但当你需要处理JsonNode、自定义值对象等「内置转换器不认识」的类型时就需要自己编写并注册一个自定义类型转换器。本文以 JSON 字段反序列化为例完整讲解自定义转换器的写法、全局 / 属性 / 类映射三种注册方式并结合 ITypeConverter.cs、DefaultTypeConverter.cs 与 TypeConverterCache.cs 等源码说明转换器被选中的底层机制与失败处理策略。读完后你将能独立实现、注册并调试任意业务类型与 CSV 字段之间的双向转换。为什么需要自定义类型转换器在读取和写入 CSV 时CSV 的每一个字段本质上都是字符串而类的每个属性都可能是任意 CLR 类型。CsvHelper 正是通过**类型转换器Type Converter**来完成「字符串 ↔ 属性值」的转换读取时调用ConvertFromString把字段文本转成对象写入时调用ConvertToString把对象序列化回文本。CsvHelper 已经内置了大量转换器覆盖绝大多数常见场景完整清单可见 type-conversion/index.md例如| CsvHelper 转换器 | C# 类型关键字 | .NET 类型 | | - | - | - | | ArrayConverter | [ ] | System.Array | | BigIntegerConverter | | System.Numerics.BigInteger | | BooleanConverter | bool | System.Boolean | | ByteArrayConverter | byte[ ] | System.Array | | ByteConverter | byte | System.Byte | | CharConverter | char | System.Char | | CollectionGenericConverter | | System.Collections.Generic.CollectionT、ListT | | DateOnlyConverter | | System.DateOnly | | DateTimeConverter | | System.DateTime | | DateTimeOffsetConverter | | System.DateTimeOffset | | DecimalConverter | decimal | System.Decimal | | DoubleConverter | double | System.Double | | EnumConverter | enum | System.Enum | | GuidConverter | | System.Guid | | IDictionaryConverter | | Dictionarystring, string | | IDictionaryGenericConverter | | DictionaryTKey, TValue | | IEnumerableConverter | | ICollection、IEnumerable、IList | | IEnumerableGenericConverter | | ICollectionT、IEnumerableT、IListT | | Int16Converter | short | System.Int16 | | Int32Converter | int | System.Int32 | | Int64Converter | long | System.Int64 | | NullableConverter | | System.NullableT | | SByteConverter | sbyte | System.SByte | | SingleConverter | float | System.Single | | StringConverter | string | System.String | | TimeOnlyConverter | | System.TimeOnly | | UInt16Converter | ushort | System.UInt16 | | UInt32Converter | uint | System.UInt32 | | UInt64Converter | ulong | System.UInt64 | | UriConverter | | System.Uri |这些内置转换器由 TypeConverterCache.cs 中的CreateDefaultConverters()在构造缓存时自动注册。但一旦遇到内置转换器无法处理的类型——例如本文示例中的System.Text.Json.Nodes.JsonNode——就需要自己实现转换器并注册。你可以按需选择三种注册方式之一全局注册、成员属性注册、类映射注册官方文档建议「三种方式只需要用其中一种」示例代码中为了演示则全部用上了。自定义转换器的基础接口与基类在动手写转换器之前先了解 CsvHelper 定义的最小契约。所有类型转换器都实现 ITypeConverter.cs 接口它只声明两个方法public interface ITypeConverter { // 读取时调用把 CSV 字段文本转换为对象 object? ConvertFromString(string? text, IReaderRow row, MemberMapData memberMapData); // 写入时调用把对象转换回字符串 string? ConvertToString(object? value, IWriterRow row, MemberMapData memberMapData); }实际开发中通常不需要从零实现该接口而是继承以下两个基类之一DefaultTypeConverter实现了接口的全部方法但ConvertFromString默认直接失败详见下文「转换失败的处理」。自定义转换器通常只重写ConvertFromString即可获得完整的默认写入能力。官方示例正是这种写法。TypeConverterT见 TypeConverter.cs强类型的泛型抽象基类需要同时实现abstract T? ConvertFromString(...)与abstract string? ConvertToString(T? value, ...)两个方向都必须自己写。编写自定义转换器JsonNode 示例官方文档给出的典型场景是CSV 的某一列存放 JSON 字符串希望读取后直接映射为System.Text.Json.Nodes.JsonNode对象。示例数据如下Id,Name,Json 1,one,{foo: bar}注意 CSV 中 JSON 字符串内的双引号需要写成两个进行转义解析后得到的Json字段文本是{foo: bar}。转换器本身非常简单——继承DefaultTypeConverter只重写ConvertFromString这一个方法用JsonSerializer.DeserializeJsonNode完成解析public class JsonNodeConverter : DefaultTypeConverter { public override object ConvertFromString(string text, IReaderRow row, MemberMapData memberMapData) { return JsonSerializer.DeserializeJsonNode(text); } }由于继承了DefaultTypeConverter写入方向无需任何代码ConvertToString的默认实现会先判断value null此时若配置了NullValues则返回首个空值标记否则返回空字符串再判断值是否实现IFormattable按TypeConverterOptions.Formats与CultureInfo格式化最后回退到ToString()——JSON 节点序列化后的字符串会被原样写回 CSV完全符合预期详见 DefaultTypeConverter.cs。三种注册方式详解方式一全局注册TypeConverterCache.AddConverter在创建CsvReader之后通过csv.Context.TypeConverterCache把转换器与目标类型JsonNode绑定。此后整个上下文范围内所有JsonNode类型的成员都会使用该转换器using (var reader new StreamReader(path\\to\\file.csv)) using (var csv new CsvReader(reader, CultureInfo.InvariantCulture)) { // 全局注册所有 JsonNode 类型的字段都走 JsonNodeConverter csv.Context.TypeConverterCache.AddConverterJsonNode(new JsonNodeConverter()); csv.Context.RegisterClassMapFooMap(); csv.GetRecordsFoo().ToList().Dump(); }从 TypeConverterCache.cs 可以看到AddConverterT(ITypeConverter)本质是向内部字典写入typeConverters[typeof(T)] typeConverter覆盖同类型的默认转换器。除泛型重载外还提供AddConverter(Type, ITypeConverter)、AddConverter(ITypeConverter)为所有已注册类型统一替换以及对应的RemoveConverter方法。方式二成员属性注册[TypeConverter]直接在属性上标注[TypeConverter(typeof(JsonNodeConverter))]特性仅对这一个成员生效public class Foo { public int Id { get; set; } public string Name { get; set; } // 注册 via attribute仅 Json 属性使用该转换器 [TypeConverter(typeof(JsonNodeConverter))] public JsonNode Json { get; set; } }这里的TypeConverterAttribute位于 Attributes/TypeConverterAttribute.cs。它同时实现了IMemberMapper与IParameterMapper因此不仅可用于属性 / 字段还可以用于构造函数参数构造时通过ObjectResolver.Current.Resolve(typeConverterType)解析转换器实例如果传入的类型没有实现ITypeConverter会抛出ArgumentException。方式三类映射注册Map TypeConverter 在ClassMapFoo中为指定成员调用TypeConverterJsonNodeConverter()public class FooMap : ClassMapFoo { public FooMap() { Map(m m.Id); Map(m m.Name); // 注册 via map Map(m m.Json).TypeConverterJsonNodeConverter(); } }MemberMapTClass, TMember.TypeConverterTConverter()的实现见 [MemberMap1.cs](https://link.gitcode.com/i/20e929ab11203d3b41ebc18892d7a19f)通过ObjectResolver.Current.Resolve ()实例化转换器再写入映射数据MemberMapData.TypeConverter。三种方式如何选择全局注册目标类型在项目中各处含义一致如统一反序列化为JsonNode使用频率最高、最省事属性注册目标类型本身不确定转换规则但某个成员有特殊需求声明式、零配置代码类映射注册已经使用ClassMap管理映射关系时最自然把所有映射规则集中在一处便于查看和维护。官方文档明确指出「只需要使用其中一种」三种方式并用只是为了演示各自的写法。底层原理CsvHelper 如何选中你的转换器理解注册背后的选择逻辑有助于排查「为什么我的转换器没生效」这类问题。核心是 TypeConverterCache.cs 中两个GetConverter重载GetConverter(MemberInfo)TypeConverterCache.cs先检查成员上是否标注了TypeConverterAttribute——属性注册优先级最高命中后直接返回该特性指定的转换器若成员上没有属性则退而调用GetConverter(Type)TypeConverterCache.cs先在字典缓存中查找即全局注册的转换器、以及内置默认转换器未命中时依次询问用户注册的ITypeConverterFactory和内置工厂EnumConverterFactory、NullableConverterFactory、CollectionConverterFactory能否创建该类型工厂创建成功后还会把结果写回缓存以便复用最终仍无法匹配时返回一个DefaultTypeConverter实例作为兜底。由此可见完整的优先级链路是成员属性 全局缓存注册 类型转换器工厂 DefaultTypeConverter 兜底。这也是为什么属性注册的效果最局部也最优先。此外ITypeConverterFactory见 ITypeConverterFactory.cs是面向一族类型的扩展点内置工厂正是靠它让枚举、可空类型、集合类型共享一套转换逻辑。如果你的自定义转换器需要批量服务于某类类型而不是单一类型实现CanCreate/Create两个方法并通过TypeConverterCache.AddConverterFactory注册即可工厂会「按注册顺序最先匹配者生效」。转换失败怎么办默认基类的失败处理DefaultTypeConverter.ConvertFromString的默认实现并不是「尝试转换」而是直接抛出TypeConverterException——除非映射数据中配置了默认值。具体逻辑见 DefaultTypeConverter.cs若memberMapData.UseDefaultOnConversionFailure为false或未设置Default值直接抛出异常若设置了Default则优先返回配置的默认值对允许为null的类型默认值本身可为null若默认值类型与成员类型不兼容仍会抛出TypeConverterException。抛出的TypeConverterException消息中包含字段文本、成员名、成员类型与转换器类型等信息并且当ExceptionMessagesContainRawData配置为false时出于安全考虑字段原文会被替换为Hidden because ExceptionMessagesContainRawData is false.见 DefaultTypeConverter.cs。因此在实现自己的ConvertFromString时如果解析可能失败请明确选择策略要么直接抛出异常由ReadingExceptionOccurred等回调处理要么返回默认值要么配合UseDefaultOnConversionFailure/Default配置让基类逻辑接管。完整的可运行示例把文档示例合并成一段完整代码略去了Dump()这类 LINQPad 特有调用using System.Text.Json.Nodes; using CsvHelper; using CsvHelper.Configuration; using CsvHelper.TypeConversion; using System.Globalization; // 1. 自定义转换器继承 DefaultTypeConverter只重写读取方向 public class JsonNodeConverter : DefaultTypeConverter { public override object ConvertFromString(string text, IReaderRow row, MemberMapData memberMapData) { return JsonSerializer.DeserializeJsonNode(text); } } public class Foo { public int Id { get; set; } public string Name { get; set; } // 注册方式二属性 [TypeConverter(typeof(JsonNodeConverter))] public JsonNode Json { get; set; } } public class FooMap : ClassMapFoo { public FooMap() { Map(m m.Id); Map(m m.Name); // 注册方式三类映射 Map(m m.Json).TypeConverterJsonNodeConverter(); } } public class Program { public static void Main() { using var reader new StreamReader(path\\to\\file.csv); using var csv new CsvReader(reader, CultureInfo.InvariantCulture); { // 注册方式一全局 csv.Context.TypeConverterCache.AddConverterJsonNode(new JsonNodeConverter()); csv.Context.RegisterClassMapFooMap(); var records csv.GetRecordsFoo().ToList(); foreach (var foo in records) { Console.WriteLine(${foo.Id}, {foo.Name}, {foo.Json[foo]}); } } } }对应的测试与验证可以参考仓库中的 TypeConverterTests.cs 与 TypeConverter1Tests.cs这些测试覆盖了基类行为与默认转换器的各类边界情况若需为自定义转换器补充单元测试直接以这两个文件为模板即可。小结自定义类型转换器是 CsvHelper 类型转换体系中面向扩展的最后一环先确认 内置转换器清单 无法覆盖需求再继承DefaultTypeConverter或TypeConverterT实现核心的ConvertFromString最后从「全局注册 / 属性注册 / 类映射注册」三种方式中任选一种接入。理解 TypeConverterCache.cs 的查询链路属性优先、缓存其次、工厂兜底之后你不仅能写出正确的自定义转换器也能快速定位转换器未生效的原因。赞分享后端数据工程【免费下载链接】CsvHelperLibrary to help reading and writing CSV files项目地址https://gitcode.com/gh_mirrors/cs/CsvHelper点击查看免费下载相关推荐AutoMapper 自定义类型转换器Custom Type Converters完全指南ITypeConverter 与 ConvertUsing 深度解析AutoMapper 自定义类型转换器Custom Type Converters完全指南ITypeConverter 与 ConvertUsing 深度后端CsvHelper 类型转换Type Conversion完全指南内置转换器、TypeConverterOptions 与自定义 TypeConverterCsvHelper 类型转换Type Conversion完全指南内置转换器、TypeConverterOptions 与自定义 TypeConverte后端数据工程MikroORM 自定义类型Custom Types完全指南从 Type 抽象类到 SQL 级转换的实战详解MikroORM 自定义类型Custom Types完全指南从 Type 抽象类到 SQL 级转换的实战详解 本文以 MikroORM 的 Type 抽象后端上一篇Playnite游戏库管理器一站式整合你的所有PC和模拟器游戏下一篇Smithbox终极指南三步打造你的专属魂系游戏体验创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表