需求背景:物件複製效能優化;同時,在物件複製時,應跳過引用型別的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左右