生成可引入其他项目的.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
相关产品推荐
相关产品推荐

