.NET框架原生按属性名自动映射POCO对象的方法咨询
当然有!.NET框架本身就提供了几种原生方案来实现POCO对象之间的属性自动映射,完全不需要依赖AutoMapper这类第三方库。下面我就给你详细拆解这些实用的方法:
1. 基于反射的手动映射(最直接的原生方式)
反射是.NET里最基础的元数据操作能力,和Database.SqlQuery的底层原理类似,都是通过属性名匹配来完成值的赋值。你可以通过反射遍历源对象的属性,再把对应的值同步到目标对象的同名属性上。
示例代码:
public static TTarget Map<TSource, TTarget>(TSource source) where TTarget : new() { var target = new TTarget(); var sourceProperties = typeof(TSource).GetProperties(BindingFlags.Public | BindingFlags.Instance); var targetProperties = typeof(TTarget).GetProperties(BindingFlags.Public | BindingFlags.Instance); foreach (var sourceProp in sourceProperties) { // 匹配同名且同类型的可写属性 var targetProp = targetProperties.FirstOrDefault(p => p.Name == sourceProp.Name && p.PropertyType == sourceProp.PropertyType); if (targetProp != null && targetProp.CanWrite) { var value = sourceProp.GetValue(source); targetProp.SetValue(target, value); } } return target; }
调用方式很简单:
var sourceObj = new SourceClass { Id = 1, Name = "Test" }; var targetObj = Map<SourceClass, TargetClass>(sourceObj);
这种方式的优势是完全原生、零依赖;缺点是反射的性能相对偏低,如果是高频调用场景,建议提前缓存反射获取到的属性信息来优化速度。
2. 用表达式树构建高性能映射函数
如果觉得反射的性能达不到要求,可以用System.Linq.Expressions构建编译后的映射委托——第一次构建完成后,后续调用的性能几乎和手动写的硬编码映射函数一样。
示例代码:
public static class Mapper<TSource, TTarget> where TTarget : new() { // 缓存编译后的映射委托 private static readonly Func<TSource, TTarget> _mapFunc = CreateMapFunc(); private static Func<TSource, TTarget> CreateMapFunc() { var sourceParam = Expression.Parameter(typeof(TSource), "source"); var targetNew = Expression.New(typeof(TTarget)); var propertyBindings = new List<MemberBinding>(); foreach (var sourceProp in typeof(TSource).GetProperties(BindingFlags.Public | BindingFlags.Instance)) { var targetProp = typeof(TTarget).GetProperty(sourceProp.Name, BindingFlags.Public | BindingFlags.Instance); // 只处理同名同类型的可写属性 if (targetProp != null && targetProp.CanWrite && targetProp.PropertyType == sourceProp.PropertyType) { var sourcePropAccess = Expression.Property(sourceParam, sourceProp); propertyBindings.Add(Expression.Bind(targetProp, sourcePropAccess)); } } var memberInit = Expression.MemberInit(targetNew, propertyBindings); return Expression.Lambda<Func<TSource, TTarget>>(memberInit, sourceParam).Compile(); } public static TTarget Map(TSource source) { return _mapFunc(source); } }
使用时直接调用静态类的方法:
var targetObj = Mapper<SourceClass, TargetClass>.Map(sourceObj);
这种方式兼顾了原生性和高性能,非常适合需要频繁进行对象映射的场景。
3. 序列化/反序列化间接映射(简单粗暴的懒人方案)
利用.NET原生的序列化库(比如System.Text.Json或System.Xml.Serialization),把源对象序列化成JSON/XML这类中间格式,再反序列化成目标对象。这种方式完全不需要写任何映射逻辑,全靠序列化库自动匹配同名属性。
示例(使用System.Text.Json):
using System.Text.Json; public static TTarget MapViaSerialization<TSource, TTarget>(TSource source) { var jsonString = JsonSerializer.Serialize(source); return JsonSerializer.Deserialize<TTarget>(jsonString); }
这种方式的优点是代码量极少,不用处理任何属性匹配细节;缺点是性能比前两种方式差一些,而且如果对象包含复杂类型或有自定义序列化需求,需要额外配置。但对于简单的POCO对象映射,这个方案足够省心。
4. 利用TypeDescriptor(适合数据绑定场景)
System.ComponentModel.TypeDescriptor是.NET原生的组件,它可以用来获取对象的属性描述符,进而完成属性值的复制。这种方式和反射类似,但在数据绑定相关的场景下会更友好。
示例代码:
using System.ComponentModel; public static void MapUsingTypeDescriptor<TSource, TTarget>(TSource source, TTarget target) { var sourceProps = TypeDescriptor.GetProperties(source); var targetProps = TypeDescriptor.GetProperties(target); foreach (PropertyDescriptor sourceProp in sourceProps) { PropertyDescriptor targetProp = targetProps[sourceProp.Name]; if (targetProp != null && targetProp.PropertyType == sourceProp.PropertyType && !targetProp.IsReadOnly) { targetProp.SetValue(target, sourceProp.GetValue(source)); } } }
使用时需要先创建好目标对象:
var targetObj = new TargetClass(); MapUsingTypeDescriptor(sourceObj, targetObj);
总结一下:如果追求极致性能,优先选表达式树的方式;如果想省事儿,序列化反序列化或基础反射方案就够用;如果是数据绑定场景,TypeDescriptor会更适配。这些都是.NET框架原生提供的能力,完全不需要依赖第三方库。
内容的提问来源于stack exchange,提问作者Omtechguy

