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

生成可引入其他项目的.cs文件,解决类间基础映射耗时问题

我太懂这种反复写字段映射代码的煎熬了——明明都是简单的赋值,却要对着两个类的字段名反复核对,既费时间还容易因为手滑写错字段名!针对这种场景,你可以写一个通用的对象映射工具类,用反射实现自动字段匹配,还支持自定义字段映射规则,完全可以打包成类库在多个项目里复用。

下面是一个完整的可复用C#实现,支持双向映射、自定义字段对应,还能直接创建并返回目标对象:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;

public static class ObjectMapper
{
    // 缓存自定义字段映射,避免重复注册
    private static readonly Dictionary<Tuple<Type, string>, Tuple<Type, string>> _customFieldMappings = new Dictionary<Tuple<Type, string>, Tuple<Type, string>>();
    // 缓存反射获取的成员信息,提升性能
    private static readonly Dictionary<Tuple<Type, string>, MemberInfo> _memberCache = new Dictionary<Tuple<Type, string>, MemberInfo>();

    /// <summary>
    /// 注册源类型到目标类型的字段/属性映射
    /// </summary>
    /// <typeparam name="TSource">源类型</typeparam>
    /// <typeparam name="TTarget">目标类型</typeparam>
    /// <param name="sourceMemberName">源字段/属性名</param>
    /// <param name="targetMemberName">目标字段/属性名</param>
    public static void RegisterFieldMapping<TSource, TTarget>(string sourceMemberName, string targetMemberName)
    {
        var sourceKey = Tuple.Create(typeof(TSource), sourceMemberName);
        var targetValue = Tuple.Create(typeof(TTarget), targetMemberName);
        
        _customFieldMappings[sourceKey] = targetValue;
        
        // 自动注册反向映射,方便双向转换
        var reverseKey = Tuple.Create(typeof(TTarget), targetMemberName);
        var reverseValue = Tuple.Create(typeof(TSource), sourceMemberName);
        _customFieldMappings[reverseKey] = reverseValue;
    }

    /// <summary>
    /// 将源对象的字段/属性值复制到目标对象
    /// </summary>
    /// <typeparam name="TSource">源类型</typeparam>
    /// <typeparam name="TTarget">目标类型</typeparam>
    /// <param name="source">源对象</param>
    /// <param name="target">目标对象</param>
    public static void Map<TSource, TTarget>(TSource source, TTarget target)
    {
        if (source == null) throw new ArgumentNullException(nameof(source));
        if (target == null) throw new ArgumentNullException(nameof(target));

        var sourceType = typeof(TSource);
        var targetType = typeof(TTarget);

        // 获取源类型的所有公共实例字段和属性
        var sourceMembers = sourceType.GetMembers(BindingFlags.Public | BindingFlags.Instance)
            .Where(m => m.MemberType is MemberTypes.Field or MemberTypes.Property);

        foreach (var sourceMember in sourceMembers)
        {
            var targetMemberName = GetTargetMemberName<TSource, TTarget>(sourceMember.Name);
            if (string.IsNullOrEmpty(targetMemberName)) continue;

            var targetMember = GetCachedMember(targetType, targetMemberName, sourceMember.MemberType);
            if (targetMember == null) continue;

            // 读取源成员的值
            var value = GetMemberValue(sourceMember, source);
            // 设置目标成员的值
            SetMemberValue(targetMember, target, value);
        }
    }

    /// <summary>
    /// 创建目标对象并将源对象的字段/属性值复制过去
    /// </summary>
    /// <typeparam name="TSource">源类型</typeparam>
    /// <typeparam name="TTarget">目标类型</typeparam>
    /// <param name="source">源对象</param>
    /// <returns>转换后的目标对象</returns>
    public static TTarget Map<TSource, TTarget>(TSource source) where TTarget : new()
    {
        var target = new TTarget();
        Map(source, target);
        return target;
    }

    // 获取目标成员名称(优先用自定义映射,没有则直接匹配)
    private static string GetTargetMemberName<TSource, TTarget>(string sourceMemberName)
    {
        var key = Tuple.Create(typeof(TSource), sourceMemberName);
        if (_customFieldMappings.TryGetValue(key, out var mapping))
        {
            return mapping.Item1 == typeof(TTarget) ? mapping.Item2 : null;
        }
        return sourceMemberName;
    }

    // 缓存并获取目标成员信息
    private static MemberInfo GetCachedMember(Type targetType, string memberName, MemberTypes memberType)
    {
        var cacheKey = Tuple.Create(targetType, memberName);
        if (_memberCache.TryGetValue(cacheKey, out var member))
        {
            return member.MemberType == memberType ? member : null;
        }

        var targetMember = targetType.GetMember(memberName, BindingFlags.Public | BindingFlags.Instance)
            .FirstOrDefault(m => m.MemberType == memberType);
        
        if (targetMember != null)
        {
            _memberCache[cacheKey] = targetMember;
        }

        return targetMember;
    }

    // 获取成员的值
    private static object GetMemberValue(MemberInfo member, object obj)
    {
        return member switch
        {
            FieldInfo field => field.GetValue(obj),
            PropertyInfo prop => prop.GetValue(obj),
            _ => null
        };
    }

    // 设置成员的值
    private static void SetMemberValue(MemberInfo member, object obj, object value)
    {
        switch (member)
        {
            case FieldInfo field:
                field.SetValue(obj, value);
                break;
            case PropertyInfo prop:
                if (prop.CanWrite) prop.SetValue(obj, value);
                break;
        }
    }
}

怎么使用这个工具类

假设你的两个类是这样的:

public class A1
{
    public string SomeField { get; set; }
    public int UserAge { get; set; }
}

public class A2
{
    public string SomeOtherField { get; set; }
    public int Age { get; set; }
}

你只需要先注册一次自定义字段映射(建议在项目启动时注册,比如Program.cs或者Startup类里),之后就可以轻松完成双向转换了:

// 注册字段映射:A1.SomeField → A2.SomeOtherField,同时自动注册反向映射
ObjectMapper.RegisterFieldMapping<A1, A2>(nameof(A1.SomeField), nameof(A2.SomeOtherField));
ObjectMapper.RegisterFieldMapping<A1, A2>(nameof(A1.UserAge), nameof(A2.Age));

// 从A1转换到A2
var a1Instance = new A1 { SomeField = "Hello", UserAge = 30 };
var a2Instance = ObjectMapper.Map<A1, A2>(a1Instance);
// a2Instance.SomeOtherField = "Hello",a2Instance.Age = 30

// 反向转换A2到A1
var a2ReverseInstance = new A2 { SomeOtherField = "World", Age = 25 };
var a1ReverseInstance = ObjectMapper.Map<A2, A1>(a2ReverseInstance);
// a1ReverseInstance.SomeField = "World",a1ReverseInstance.UserAge = 25

可以扩展的优化点

  • 类型转换支持:如果需要处理不同类型的字段(比如int转string,DateTime转string),可以在SetMemberValue方法里添加类型转换逻辑,或者注册自定义类型转换器。
  • 命名约定自动匹配:如果你的字段命名有规律(比如A1的SomeField对应A2的SomeFieldDto),可以添加自动匹配规则,不用手动注册每个字段。
  • 忽略特定字段:添加忽略字段的方法,避免某些字段被映射。

你可以把这个类放到一个单独的类库项目里,编译成DLL,之后在任何需要的项目里引用这个DLL,就能直接使用这个映射工具,再也不用手动写重复的转换代码了!

内容的提问来源于stack exchange,提问作者Ricardo Alves

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.26 09:13:48