C#中不使用ICloneable接口如何克隆外部库的引用类型对象?
克隆外部库对象的可行方案(无需逐个赋值或修改原类)
由于你无法修改外部库中的Point类,也不想手动逐个复制属性,以下几种方法可以实现对象克隆,确保修改副本时不影响原对象:
1. 序列化反序列化法
通过将对象序列化为JSON再反序列化,生成独立的对象副本。这种方法无需关注对象的具体属性,实现简单。
示例代码(使用System.Text.Json):
using System.Text.Json; public static class CloneExtensions { public static T DeepClone<T>(this T source) where T : class { var jsonString = JsonSerializer.Serialize(source); return JsonSerializer.Deserialize<T>(jsonString); } } // 使用方式 Point p1 = new Point(5, 6); Point p2 = p1.DeepClone(); p2.X = 9; // p1的X值仍为5,不受修改p2的影响
注意事项:
- 此方法适用于大多数公共属性可访问的对象,无需原类标记
[Serializable](System.Text.Json默认支持)。 - 性能略低于反射,但实现成本极低,适合非高频克隆场景。
2. 反射自动复制属性
利用反射遍历对象的公共可读写属性,自动将原对象的属性值复制到新实例中,避免手动逐个赋值。
基础版本代码:
using System.Reflection; public static class CloneExtensions { public static T ShallowClone<T>(this T source) where T : class, new() { var clone = new T(); foreach (PropertyInfo prop in typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance)) { if (prop.CanRead && prop.CanWrite) { prop.SetValue(clone, prop.GetValue(source)); } } return clone; } } // 使用方式 Point p1 = new Point(5, 6); Point p2 = p1.ShallowClone(); p2.X = 9; // p1保持原始状态
性能优化版本(缓存反射信息):
如果需要频繁克隆对象,可以缓存反射获取的属性信息,避免重复反射开销:
using System.Reflection; using System.Collections.Generic; public static class CloneExtensions { private static readonly Dictionary<Type, PropertyInfo[]> _propertyCache = new Dictionary<Type, PropertyInfo[]>(); public static T ShallowClone<T>(this T source) where T : class, new() { Type type = typeof(T); if (!_propertyCache.TryGetValue(type, out PropertyInfo[] props)) { props = type.GetProperties(BindingFlags.Public | BindingFlags.Instance) .Where(p => p.CanRead && p.CanWrite) .ToArray(); _propertyCache[type] = props; } var clone = new T(); foreach (var prop in props) { prop.SetValue(clone, prop.GetValue(source)); } return clone; } }
3. 表达式树编译(高性能替代反射)
通过表达式树编译成委托,性能接近手动赋值,适合需要高频克隆的场景。
示例代码:
using System.Linq.Expressions; using System.Collections.Generic; public static class CloneExtensions { private static readonly Dictionary<Type, Delegate> _cloneDelegateCache = new Dictionary<Type, Delegate>(); public static T ShallowClone<T>(this T source) where T : class, new() { Type type = typeof(T); if (!_cloneDelegateCache.TryGetValue(type, out Delegate cloneDelegate)) { ParameterExpression sourceParam = Expression.Parameter(type, "source"); NewExpression newInstance = Expression.New(type); List<MemberBinding> propertyBindings = new List<MemberBinding>(); foreach (var prop in type.GetProperties(BindingFlags.Public | BindingFlags.Instance) .Where(p => p.CanRead && p.CanWrite)) { MemberExpression sourcePropExpr = Expression.Property(sourceParam, prop); propertyBindings.Add(Expression.Bind(prop, sourcePropExpr)); } MemberInitExpression initExpr = Expression.MemberInit(newInstance, propertyBindings); cloneDelegate = Expression.Lambda<Func<T, T>>(initExpr, sourceParam).Compile(); _cloneDelegateCache[type] = cloneDelegate; } return ((Func<T, T>)cloneDelegate)(source); } } // 使用方式 Point p1 = new Point(5, 6); Point p2 = p1.ShallowClone(); p2.X = 9; // p1的X值不会被修改
针对你的测试代码的效果
原代码中Point p2 = p1;是引用赋值,修改p2.X会直接影响p1。使用上述任意一种克隆方法后,p2是独立的实例,修改其属性不会改变p1的状态,完全符合你的需求。
内容的提问来源于stack exchange,提问作者ozziem
相关产品推荐
相关产品推荐

