C# 高性能对象复制
需求背景:对象复制性能优化;同时,在对象复制时,应跳过引用类型的null值复制,值类型支持值类型向可空类型的复制
——————————————
1 using Common; 2 using System; 3 4 class Program 5 { 6 static void Main(string[] args) 7 { 8 TestClassA classA = new TestClassA() { PropA = new TestClass() { Name = "cs1" }, PropB = "c1", PropC = 1 }; 9 TestClassA classB = new TestClassA() { PropA = new TestClass() { Name = "cs2" }, PropB = "c2", PropC = 2 }; 10 FastCopy.Copy(classA, classB, false); 11 Console.WriteLine(classB.PropA?.Name + ":" + classB.PropB + ":" + classB.PropC); 12 13 TestClassA classC = new TestClassA() { PropA = new TestClass() { Name = "cs1" } }; 14 TestClassA classD = new TestClassA() { PropA = new TestClass() { Name = "cs2" }, PropB = "c2", PropC = 2 }; 15 FastCopy.Copy(classC, classD, false); 16 Console.WriteLine(classD.PropA?.Name + ":" + classD.PropB + ":" + classD.PropC); 17 } 18 } 19 public class TestClassA 20 { 21 public TestClass PropA { get; set; } 22 public string PropB { get; set; } 23 public int? PropC { get; set; } 24 } 25 public class TestClass 26 { 27 public string Name { get; set; } 28 }
输出:
百万次调用耗时:270-300ms
1 using System; 2 using System.Collections.Concurrent; 3 using System.Collections.Generic; 4 using System.Linq; 5 using System.Linq.Expressions; 6 using System.Reflection; 7 using static System.Linq.Expressions.Expression; 8 9 namespace Common 10 { 11 public static class FastCopy 12 { 13 static ConcurrentDictionary<string, object> copiers = new ConcurrentDictionary<string, object>(); 14 15 /// <summary> 16 /// 复制两个对象同名属性值 17 /// </summary> 18 /// <typeparam name="S"></typeparam> 19 /// <typeparam name="T"></typeparam> 20 /// <param name="source">源对象</param> 21 /// <param name="target">目标对象</param> 22 /// <param name="copyNull">源对象属性值为null时,是否将值复制给目标对象</param> 23 public static void Copy<S, T>(S source, T target, bool copyNull = true) 24 { 25 string name = string.Format("{0}_{1}_{2}", typeof(S), typeof(T), copyNull); 26 27 object targetCopier; 28 if (!copiers.TryGetValue(name, out targetCopier)) 29 { 30 Action<S, T> copier = CreateCopier<S, T>(copyNull); 31 copiers.TryAdd(name, copier); 32 targetCopier = copier; 33 } 34 35 Action<S, T> action = (Action<S, T>)targetCopier; 36 action(source, target); 37 } 38 39 /// <summary> 40 /// 为指定的两种类型编译生成属性复制委托 41 /// </summary> 42 /// <typeparam name="S"></typeparam> 43 /// <typeparam name="T"></typeparam> 44 /// <param name="copyNull">源对象属性值为null时,是否将值复制给目标对象</param> 45 /// <returns></returns> 46 private static Action<S, T> CreateCopier<S, T>(bool copyNull) 47 { 48 ParameterExpression source = Parameter(typeof(S)); 49 ParameterExpression target = Parameter(typeof(T)); 50 var sourceProps = typeof(S).GetProperties(BindingFlags.Instance | BindingFlags.Public).Where(p => p.CanRead).ToList(); 51 var targetProps = typeof(T).GetProperties(BindingFlags.Instance | BindingFlags.Public).Where(p => p.CanWrite).ToList(); 52 53 // 查找可进行赋值的属性 54 var copyProps = targetProps.Where(tProp => sourceProps.Where(sProp => sProp.Name == tProp.Name// 名称一致 且 55 && ( 56 sProp.PropertyType == tProp.PropertyType// 属性类型一致 或 57 || sProp.PropertyType.IsAssignableFrom(tProp.PropertyType) // 源属性类型 为 目标属性类型 的 子类;eg:object target = string source; 或 58 || (tProp.PropertyType.IsValueType && sProp.PropertyType.IsValueType && // 属性为值类型且基础类型一致,但目标属性为可空类型 eg:int? num = int num; 59 ((tProp.PropertyType.GenericTypeArguments.Length > 0 ? tProp.PropertyType.GenericTypeArguments[0] : tProp.PropertyType) == sProp.PropertyType)) 60 )).Count() > 0); 61 62 List<Expression> expressionList = new List<Expression>(); 63 foreach (var prop in copyProps) 64 { 65 if (prop.PropertyType.IsValueType)// 属性为值类型 66 { 67 PropertyInfo sProp = typeof(S).GetProperty(prop.Name); 68 PropertyInfo tProp = typeof(T).GetProperty(prop.Name); 69 if (sProp.PropertyType == tProp.PropertyType)// 属性类型一致 eg:int num = int num; 或 int? num = int? num; 70 { 71 var assign = Assign(Property(target, prop.Name), Property(source, prop.Name)); 72 expressionList.Add(assign); 73 } 74 else if (sProp.PropertyType.GenericTypeArguments.Length <= 0 && tProp.PropertyType.GenericTypeArguments.Length > 0)// 属性类型不一致且目标属性类型为可空类型 eg:int? num = int num; 75 { 76 var convert = Convert(Expression.Property(source, prop.Name), tProp.PropertyType); 77 var cvAssign = Assign(Expression.Property(target, prop.Name), convert); 78 expressionList.Add(cvAssign); 79 } 80 } 81 else// 属性为引用类型 82 { 83 var assign = Assign(Property(target, prop.Name), Property(source, prop.Name));// 编译生成属性赋值语句 target.{PropertyName} = source.{PropertyName}; 84 var sourcePropIsNull = Equal(Constant(null, prop.PropertyType), Property(source, prop.Name));// 判断源属性值是否为Null;编译生成 source.{PropertyName} == null 85 var setNull = IsTrue(Constant(copyNull));// 判断是否复制Null值 编译生成 copyNull == True 86 var setNullTest = IfThen(setNull, assign); 87 var condition = IfThenElse(sourcePropIsNull, setNullTest, assign); 88 89 /** 90 * 编译生成 91 * if(source.{PropertyName} == null) 92 * { 93 * if(setNull) 94 * { 95 * target.{PropertyName} = source.{PropertyName}; 96 * } 97 * } 98 * else 99 * { 100 * target.{PropertyName} = source.{PropertyName}; 101 * } 102 */ 103 expressionList.Add(condition); 104 } 105 } 106 var block = Block(expressionList.ToArray()); 107 Expression<Action<S, T>> lambda = Lambda<Action<S, T>>(block, source, target); 108 return lambda.Compile(); 109 } 110 } 111 }
如果完整复制,去掉逻辑判断,同时可通过泛型类,不在使用字典,性能还可以提升。
1 using System; 2 using System.Linq; 3 using System.Linq.Expressions; 4 using System.Reflection; 5 6 namespace Common 7 { 8 public static class FastCopy<S, T> 9 { 10 static Action<S, T> action = CreateCopier(); 11 /// <summary> 12 /// 复制两个对象同名属性值 13 /// </summary> 14 /// <typeparam name="S"></typeparam> 15 /// <typeparam name="T"></typeparam> 16 /// <param name="source">源对象</param> 17 /// <param name="target">目标对象</param> 18 /// <param name="copyNull">源对象属性值为null时,是否将值复制给目标对象</param> 19 public static void Copy(S source, T target, bool copyNull = true) 20 { 21 action(source, target); 22 } 23 24 /// <summary> 25 /// 为指定的两种类型编译生成属性复制委托 26 /// </summary> 27 /// <typeparam name="S"></typeparam> 28 /// <typeparam name="T"></typeparam> 29 /// <param name="copyNull">源对象属性值为null时,是否将值复制给目标对象</param> 30 /// <returns></returns> 31 private static Action<S, T> CreateCopier() 32 { 33 ParameterExpression source = Expression.Parameter(typeof(S)); 34 ParameterExpression target = Expression.Parameter(typeof(T)); 35 var sourceProps = typeof(S).GetProperties(BindingFlags.Instance | BindingFlags.Public).Where(p => p.CanRead).ToList(); 36 var targetProps = typeof(T).GetProperties(BindingFlags.Instance | BindingFlags.Public).Where(p => p.CanWrite).ToList(); 37 38 // 查找可进行赋值的属性 39 var copyProps = targetProps.Where(tProp => sourceProps.Where(sProp => sProp.Name == tProp.Name// 名称一致 且 40 && ( 41 sProp.PropertyType == tProp.PropertyType// 属性类型一致 42 )).Count() > 0); 43 44 var block = Expression.Block(from p in copyProps select Expression.Assign(Expression.Property(target, p.Name), Expression.Property(source, p.Name))); 45 Expression<Action<S, T>> lambda = Expression.Lambda<Action<S, T>>(block, source, target); 46 return lambda.Compile(); 47 } 48 } 49 }
百万次耗时:100ms左右