如何使用CSVHelper动态写入动态类型属性至CSV文件?
问题描述
现有如下C#接口与实现类:
public interface IStateInformation { public int TaskId { get; set; } public string Name { get; set; } } public class ExternalStateInformation : IStateInformation { public int TaskId { get; set; } public string Name { get; set; } public string External { get; set; } }
需要写入CSV的SwitchingData类定义如下:
public class SwitchingData { public string DateTime { get; set; } public int EpisodeId { get; set; } public IStateInformation SourceState { get; set; } public IStateInformation TargetState { get; set; } }
使用CSVHelper写入SwitchingData对象时,即便SourceState或TargetState的实际类型是ExternalStateInformation,也只会写入IStateInformation接口定义的TaskId和Name属性,导致External属性丢失。尝试通过ClassMap解决但未成功,写入代码如下:
... using (var stream = File.Open(path, FileMode.Append)) using (var writer = new StreamWriter(stream)) using (var csv = new CsvWriter(writer, config)) { if(type != null) { csv.Context.RegisterClassMap(type); } csv.Context.TypeConverterOptionsCache.AddOptions<DateTime>(options); csv.Context.TypeConverterOptionsCache.AddOptions<DateTime?>(options); csv.WriteRecords(data); } ...
需求:实现写入对象实际类型的所有属性,而非静态接口定义的属性。
解决方案
方案1:自定义类型转换器
创建针对IStateInformation的自定义转换器,写入时自动识别对象实际类型,反射获取所有属性并格式化输出:
public class StateInformationConverter : DefaultTypeConverter { public override string ConvertToString(object value, IWriterRow row, MemberMapData memberMapData) { if (value == null) return string.Empty; // 获取对象实际类型的所有公共实例属性 var actualType = value.GetType(); var properties = actualType.GetProperties(BindingFlags.Public | BindingFlags.Instance); // 处理属性值,包含逗号或引号时用双引号包裹,同时转义内部双引号 var formattedValues = properties.Select(p => { var propValue = p.GetValue(value)?.ToString() ?? string.Empty; return propValue.Contains(',') || propValue.Contains('"') ? $"\"{propValue.Replace("\"", "\"\"")}\"" : propValue; }); return string.Join(",", formattedValues); } }
注册转换器后执行写入:
var config = new CsvConfiguration(CultureInfo.InvariantCulture); // 为IStateInformation接口注册自定义转换器 config.TypeConverterCache.AddConverter<IStateInformation>(new StateInformationConverter()); using (var stream = File.Open(path, FileMode.Append)) using (var writer = new StreamWriter(stream)) using (var csv = new CsvWriter(writer, config)) { // 保留原有配置逻辑 if(type != null) { csv.Context.RegisterClassMap(type); } csv.Context.TypeConverterOptionsCache.AddOptions<DateTime>(options); csv.Context.TypeConverterOptionsCache.AddOptions<DateTime?>(options); csv.WriteRecords(data); }
方案2:ClassMap中动态判断类型
通过ClassMap的ConvertUsing方法,针对SourceState和TargetState字段动态判断实际类型,返回包含所有属性的匿名对象:
public class SwitchingDataMap : ClassMap<SwitchingData> { public SwitchingDataMap() { // 映射基础字段 Map(m => m.DateTime); Map(m => m.EpisodeId); // 处理SourceState字段 Map(m => m.SourceState).ConvertUsing(row => { var state = row.Source.SourceState; if (state is ExternalStateInformation externalState) { return new { externalState.TaskId, externalState.Name, externalState.External }; } // 若有其他IStateInformation实现类,可在此添加对应处理逻辑 return state; }); // 处理TargetState字段,逻辑与SourceState一致 Map(m => m.TargetState).ConvertUsing(row => { var state = row.Source.TargetState; if (state is ExternalStateInformation externalState) { return new { externalState.TaskId, externalState.Name, externalState.External }; } return state; }); } }
注册该Map后执行写入:
using (var stream = File.Open(path, FileMode.Append)) using (var writer = new StreamWriter(stream)) using (var csv = new CsvWriter(writer, config)) { csv.Context.RegisterClassMap<SwitchingDataMap>(); // 保留原有配置逻辑 csv.Context.TypeConverterOptionsCache.AddOptions<DateTime>(options); csv.Context.TypeConverterOptionsCache.AddOptions<DateTime?>(options); csv.WriteRecords(data); }
方案3:自定义多态引用转换器
通过自定义ReferenceConverter,让CSVHelper自动识别接口字段的实际类型并映射所有属性:
// 自定义多态引用转换器 public class PolymorphicReferenceConverter : ReferenceConverter { public override ITypeConverter GetConverter(Type type, IWriterRow row, MemberMapData memberMapData) { // 若字段类型是接口,获取对象实际类型并返回对应转换器 if (type.IsInterface && memberMapData.Member.GetValue(row.Source) is object actualValue) { return row.Context.TypeConverterCache.GetConverter(actualValue.GetType()); } return base.GetConverter(type, row, memberMapData); } }
配置并使用该转换器:
var config = new CsvConfiguration(CultureInfo.InvariantCulture) { // 指定自定义的多态引用转换器 ReferenceConverter = new PolymorphicReferenceConverter() }; using (var stream = File.Open(path, FileMode.Append)) using (var writer = new StreamWriter(stream)) using (var csv = new CsvWriter(writer, config)) { // 保留原有配置逻辑 if(type != null) { csv.Context.RegisterClassMap(type); } csv.Context.TypeConverterOptionsCache.AddOptions<DateTime>(options); csv.Context.TypeConverterOptionsCache.AddOptions<DateTime?>(options); csv.WriteRecords(data); }
内容的提问来源于stack exchange,提问作者Ling
相关产品推荐
相关产品推荐

