You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何使用CsvHelper实现嵌套类C的通用映射?避免重复编码

问题需求

现有类结构如下:

class A
{
    public C MyC;
    public int OtherStuff;
}

class B
{
    public C MyC1;
    public C MyC2;
}

class C
{
    public int WeirdInt;
    public string WeirdString;
}

需要为类C定义一套通用的CSV映射规则,在转换List<A>或List<B>到CSV时自动应用,避免重复编写C类的映射代码。

期望输出的CSV表头:

  • 转换List<A>时:
    MyC_WeirdInt, MyC_WeirdString, OtherStuff
    
  • 转换List<B>时:
    MyC1_WeirdInt, MyC1_WeirdString, MyC2_WeirdInt, MyC2_WeirdString
    

当前实现方式是分别编写AMap和BMap,但存在大量重复的C类映射代码:

class AMap : ClassMap<A>
{
    public AMap()
    {
        Map(m => m.MyC.WeirdInt).Name("MyC_WeirdInt");
        Map(m => m.MyC.WeirdString).Name("MyC_WeirdString");
        Map(m => m.OtherStuff);
    }
}

class BMap : ClassMap<B>
{
    public BMap()
    {
        Map(m => m.MyC1.WeirdInt).Name("MyC1_WeirdInt");
        Map(m => m.MyC1.WeirdString).Name("MyC1_WeirdString");
        Map(m => m.MyC2.WeirdInt).Name("MyC2_WeirdInt");
        Map(m => m.MyC2.WeirdString).Name("MyC2_WeirdString");
    }
}
解决方案

可以通过扩展方法封装C类的通用映射逻辑,在各个ClassMap中直接调用,彻底消除重复代码。

步骤1:封装C类的通用映射扩展

public static class CMapExtensions
{
    public static void MapC<T>(this ClassMap<T> map, Expression<Func<T, C>> propertyAccessor)
    {
        // 提取属性名称作为表头前缀
        var memberExpr = (MemberExpression)propertyAccessor.Body;
        var propertyName = memberExpr.Member.Name;

        // 统一映射C类的两个字段
        map.Map(m => propertyAccessor.Compile()(m).WeirdInt)
           .Name($"{propertyName}_WeirdInt");
        map.Map(m => propertyAccessor.Compile()(m).WeirdString)
           .Name($"{propertyName}_WeirdString");
    }
}

步骤2:简化原有映射类的实现

现在只需要在AMap和BMap中调用扩展方法即可,无需重复写C类的映射代码:

class AMap : ClassMap<A>
{
    public AMap()
    {
        this.MapC(m => m.MyC); // 自动用MyC作为前缀
        Map(m => m.OtherStuff);
    }
}

class BMap : ClassMap<B>
{
    public BMap()
    {
        this.MapC(m => m.MyC1); // 自动用MyC1作为前缀
        this.MapC(m => m.MyC2); // 自动用MyC2作为前缀
    }
}

进阶:自动识别所有C类型属性(可选)

如果想更省心,不需要手动逐个调用MapC,可以通过反射自动遍历类中所有C类型的属性并添加映射:

public static void MapAllCProperties<T>(this ClassMap<T> map)
{
    // 反射获取当前类中所有类型为C的属性
    var cProperties = typeof(T).GetProperties()
                               .Where(p => p.PropertyType == typeof(C));

    foreach (var prop in cProperties)
    {
        // 构建属性访问的表达式树
        var param = Expression.Parameter(typeof(T), "m");
        var propAccess = Expression.Property(param, prop);
        var lambda = Expression.Lambda<Func<T, C>>(propAccess, param);
        
        map.MapC(lambda);
    }
}

此时映射类可以进一步简化成:

class AMap : ClassMap<A>
{
    public AMap()
    {
        this.MapAllCProperties(); // 自动处理所有C类型属性
        Map(m => m.OtherStuff);
    }
}

class BMap : ClassMap<B>
{
    public BMap()
    {
        this.MapAllCProperties(); // 自动处理所有C类型属性
    }
}

这样不管类中有多少个C类型的属性,都能自动套用统一的映射规则,完全避免重复代码。

内容的提问来源于stack exchange,提问作者狸子Neazle

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.17 02:31:09