.NET如何实现通用的‘复制到引用’对象扩展方法?
我完全懂你的需求——不想创建新对象替换List里的元素,而是直接把源对象的所有属性值,批量复制到List中已存在的那个对象引用里,省得手动一个个属性赋值对吧?这确实可以实现通用的扩展方法,下面给你两种实用方案:
方案一:反射实现(简单易上手)
反射是最直接的通用实现方式,它可以动态获取对象的属性信息,然后批量赋值。我们可以写一个扩展方法,适用于所有类:
public static class ObjectExtensions { // 扩展方法:把源对象的属性值复制到目标对象引用中 public static void CopyPropertiesTo<T>(this T source, T target) { // 空值校验 if (source == null) throw new ArgumentNullException(nameof(source)); if (target == null) throw new ArgumentNullException(nameof(target)); // 获取类型的公共实例属性:只取可读可写、非索引器的属性 var properties = typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance) .Where(p => p.CanRead && p.CanWrite && !p.GetIndexParameters().Any()); // 逐个复制属性值 foreach (var prop in properties) { var value = prop.GetValue(source); prop.SetValue(target, value); } } }
使用示例
假设你有一个Person类,List里已经有一些Person对象:
public class Person { public int Id { get; set; } public string Name { get; set; } public int Age { get; set; } } // 场景:从List中找到目标对象,把源数据复制进去 var sourcePerson = new Person { Name = "Bob", Age = 28 }; var targetPerson = myPersonList.First(p => p.Id == 101); // 拿到List里已存在的对象引用 // 调用扩展方法 sourcePerson.CopyPropertiesTo(targetPerson); // 现在targetPerson的Name和Age已经更新,而且它还是List里的那个对象,List不需要做任何替换操作
方案二:表达式树实现(性能更优)
反射每次调用都会遍历属性,性能略差。如果你的代码需要高频调用这个复制方法,可以用表达式树——它会在第一次调用时编译好赋值逻辑,后续直接执行编译后的委托,性能和手动赋值差不多:
public static class ObjectExtensions { // 缓存编译好的复制委托,避免重复编译 private static readonly Dictionary<Type, Action<object, object>> _copyActionCache = new Dictionary<Type, Action<object, object>>(); public static void CopyPropertiesTo<T>(this T source, T target) { if (source == null) throw new ArgumentNullException(nameof(source)); if (target == null) throw new ArgumentNullException(nameof(target)); var type = typeof(T); // 先从缓存取,没有的话就编译新的委托 if (!_copyActionCache.TryGetValue(type, out var copyAction)) { copyAction = CreateCopyAction<T>(); _copyActionCache[type] = copyAction; } // 执行复制逻辑 copyAction(source, target); } // 动态创建赋值的表达式树并编译 private static Action<object, object> CreateCopyAction<T>() { var sourceParam = Expression.Parameter(typeof(object), "source"); var targetParam = Expression.Parameter(typeof(object), "target"); // 把object类型的参数强转成T类型 var sourceCast = Expression.Convert(sourceParam, typeof(T)); var targetCast = Expression.Convert(targetParam, typeof(T)); // 生成所有属性的赋值表达式 var assignments = typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance) .Where(p => p.CanRead && p.CanWrite && !p.GetIndexParameters().Any()) .Select(p => Expression.Assign( Expression.Property(targetCast, p), Expression.Property(sourceCast, p) )); // 把所有赋值表达式打包成一个代码块 var body = Expression.Block(assignments); // 编译成委托 var lambda = Expression.Lambda<Action<object, object>>(body, sourceParam, targetParam); return lambda.Compile(); } }
额外说明
- 这两种方法都是浅拷贝:如果属性是引用类型(比如另一个类、List等),复制的是对象引用,而不是创建新的子对象。如果需要深拷贝,你可以在属性遍历的时候额外处理引用类型的属性(比如递归调用这个方法)。
- 如果有个别属性不想被复制,可以自定义一个特性(比如
[SkipCopy]),然后在过滤属性的时候排除带有这个特性的属性。
内容的提问来源于stack exchange,提问作者xDGameStudios
相关产品推荐
相关产品推荐

