第十五节:Expression表达式目录树(与委托的区别、自行拼接、总结几类实例间的拷贝)
一. 基本介绍
回忆: 最早接触到表达式目录树(Expression)可能要追溯到几年前使用EF早期的时候,发现where方法里的参数是Expression<Func<T,bool>>这么一个类型,当初不是很理解,只是知道传入lambda表达式使用即可,对于Expression和里面的Func<T,bool>到底是怎么一种关系,都不清楚。
今天,带着回忆开发初期的心情,详细的介绍一下这一段时间对Expression的理解。
1. Expression与Func委托的区别
①:委托是一种类型,是方法的抽象,通过委托可以将方法以参数的形式传递给另一个方法,同时调用委托的时候,它缩包含的方法都会被实现。委托的关键字是delegate,可以自定义委托,也可以使用内置委托,通过简化,可以将Lambda表达式或Lambda语句赋值给委托,委托的调用包括同步调用和异步调用。
②:表达式目录树(Expression),是一种数据结构,可以利用Lambda表达式进行声明,Lambda表达式的规则要符合Expression中Func委托的参数规则。
可以利用Lambda表达式进行声明,但Lambda语句是不能声明的。
Expression调用Compile方法可以转换成<TDelegate>中的委托。
回顾委托的代码:
1 { 2 //1. Func委托,必须要有返回值,最后一个参数为返回值,前面为输入参数 3 Func<int, int, int> func1 = new Func<int, int, int>((int m, int n) => 4 { 5 return m * n + 2; 6 }); 7 //对其进行最简化(Lambda语句) 8 Func<int, int, int> func2 = (m, n) => 9 { 10 return m * n + 2; 11 }; 12 //对其进行最简化(Lambda表达式) 13 Func<int, int, int> func3 = (m, n) => m * n + 2; 14 //调用委托 15 int result1 = func1.Invoke(2, 3); 16 int result2 = func2.Invoke(2, 3); 17 int result3 = func3.Invoke(2, 3); 18 Console.WriteLine("委托三种形式结果分别为:{0},{1},{2}", result1, result2, result3); 19 }
初识Expression表达式目录树的代码
1 { 2 //报错 (Lambda语句无法转换成表达式目录树) 3 //Expression<Func<int, int, int>> exp1 = (m, n) => 4 //{ 5 // return m * n + 2; 6 //}; 7 8 //利用Lambda表达式 来声明 表达式目录树 9 Expression<Func<int, int, int>> exp2 = (m, n) => m * n + 2; 10 11 //利用Compile编译,可以将表达式目录树转换成委托 12 Func<int, int, int> func = exp2.Compile(); 13 int result1 = func.Invoke(2, 3); 14 Console.WriteLine("表达式目录树转换成委托后结果为:{0}", result1); 15 }
执行结果
2. 自己拼接表达式目录树
①. 核心:先把Lambda表达式写出来,然后用ILSpy工具进行编译,就能把表达式目录树的拼接过程给显示出来,当然有些代码不是我们想要的,需要转换成C#代码。
②. 了解拼接要用到的类型和方法:
类型:常量值表达式(ConstantExpression)、命名参数表达式(ParameterExpression)、含二元运算符的表达式(BinaryExpression)
方法:设置常量表达式的值(Constant方法)、设置参数表达式的变量(Parameter方法)、相加(Add方法)、相乘(Multiply方法)、Lambda方法:将拼接的二元表达式转换成表达式目录树
③. 步骤:声明变量和常量→进行二元计算→将最终二元运算符的表达式转换成表达式目录树
下面分享拼接 Expression<Func<int, int, int>> express1 = (m, n) => m * n + 2; 的代码:
{ Func<int, int, int> func1 = (m, n) => m * n + 2; //利用反编译工具翻译下面这个Expression Expression<Func<int, int, int>> express1 = (m, n) => m * n + 2; //下面是反编译以后的代码(自己稍加改进了一下) ParameterExpression parameterExpression = Expression.Parameter(typeof(int), "m"); ParameterExpression parameterExpression2 = Expression.Parameter(typeof(int), "n"); BinaryExpression binaryExpression = Expression.Multiply(parameterExpression, parameterExpression2); ConstantExpression constantExpression = Expression.Constant(2, typeof(int)); BinaryExpression binaryFinalBody = Expression.Add(binaryExpression, constantExpression); Expression<Func<int, int, int>> express = Expression.Lambda<Func<int, int, int>>(binaryFinalBody, new ParameterExpression[] { parameterExpression, parameterExpression2 }); int result = express.Compile().Invoke(2, 3); Console.WriteLine("自己拼接的表达式目录树的结果为:{0}", result); }
运行结果:
二. 实体间Copy赋值的几类处理方案
背景: 在实际开发中,我们可能经常会遇到这种场景,两个实体的名称不同,属性完全相同,需要将一个实体的值赋值给另一个对应实体上的属性。
解决这类问题通常有以下几种方案:
1. 直接硬编码的形式:速度最快(0.126s)
2. 通过反射遍历属性的形式 (6.328s)
3. 利用序列化和反序列化的形式:将复制实体序列化字符串,在把该字符串反序列化被赋值实体(7.768s)
4. 字典缓存+表达式目录树(Lambda的拼接代码了解即可) (0.663s)
5. 泛型缓存+表达式目录树(Lambda的拼接代码了解即可) (2.134s)
代码分享:
1 public static class CopyUtils 2 { 3 //字典缓存 4 private static Dictionary<string, object> _Dic = new Dictionary<string, object>(); 5 6 public static void Show() 7 { 8 //0. 准备实体 9 User user = new User() 10 { 11 id = 1, 12 userName = "ypf", 13 userAge = 3 14 }; 15 long time1 = 0; 16 long time2 = 0; 17 long time3 = 0; 18 long time4 = 0; 19 long time5 = 0; 20 21 #region 1-直接硬编码的形式 22 { 23 Task.Run(() => 24 { 25 Stopwatch watch = new Stopwatch(); 26 watch.Start(); 27 for (int i = 0; i < 1000000; i++) 28 { 29 UserCopy userCopy = new UserCopy() 30 { 31 id = user.id, 32 userName = user.userName, 33 userAge = user.userAge, 34 }; 35 } 36 watch.Stop(); 37 time1 = watch.ElapsedMilliseconds; 38 Console.WriteLine("方案1所需要的时间为:{0}", time1); 39 }); 40 41 } 42 #endregion 43 44 #region 2-反射遍历属性 45 { 46 Task.Run(() => 47 { 48 Stopwatch watch = new Stopwatch(); 49 watch.Start(); 50 for (int i = 0; i < 1000000; i++) 51 { 52 CopyUtils.ReflectionMapper<User, UserCopy>(user); 53 } 54 watch.Stop(); 55 time2 = watch.ElapsedMilliseconds; 56 Console.WriteLine("方案2所需要的时间为:{0}", time2); 57 }); 58 } 59 #endregion 60 61 #region 3-序列化和反序列化 62 { 63 Task.Run(() => 64 { 65 Stopwatch watch = new Stopwatch(); 66 watch.Start(); 67 for (int i = 0; i < 1000000; i++) 68 { 69 CopyUtils.SerialzerMapper<User, UserCopy>(user); 70 } 71 watch.Stop(); 72 time3 = watch.ElapsedMilliseconds; 73 Console.WriteLine("方案3所需要的时间为:{0}", time3); 74 }); 75 76 } 77 #endregion 78 79 #region 04-字典缓存+表达式目录树 80 { 81 Task.Run(() => 82 { 83 Stopwatch watch = new Stopwatch(); 84 watch.Start(); 85 for (int i = 0; i < 1000000; i++) 86 { 87 CopyUtils.DicExpressionMapper<User, UserCopy>(user); 88 } 89 watch.Stop(); 90 time4 = watch.ElapsedMilliseconds; 91 Console.WriteLine("方案4所需要的时间为:{0}", time4); 92 }); 93 } 94 #endregion 95 96 #region 05-泛型缓存+表达式目录树 97 { 98 Task.Run(() => 99 { 100 Stopwatch watch = new Stopwatch(); 101 watch.Start(); 102 for (int i = 0; i < 1000000; i++) 103 { 104 GenericExpressionMapper<User, UserCopy>.Trans(user); 105 } 106 watch.Stop(); 107 time5 = watch.ElapsedMilliseconds; 108 Console.WriteLine("方案5所需要的时间为:{0}", time5); 109 }); 110 } 111 #endregion 112 113 }
上述代码涉及到的几个封装
1 #region 封装-反射的方式进行实体间的赋值 2 /// <summary> 3 /// 反射的方式进行实体间的赋值 4 /// </summary> 5 /// <typeparam name="TIn">赋值的实体类型</typeparam> 6 /// <typeparam name="TOut">被赋值的实体类型</typeparam> 7 /// <param name="tIn"></param> 8 public static TOut ReflectionMapper<TIn, TOut>(TIn tIn) 9 { 10 TOut tOut = Activator.CreateInstance<TOut>(); 11 //外层遍历获取【被赋值的实体类型】的属性 12 foreach (var itemOut in tOut.GetType().GetProperties()) 13 { 14 //内层遍历获取【赋值的实体类型】的属性 15 foreach (var itemIn in tIn.GetType().GetProperties()) 16 { 17 if (itemOut.Name.Equals(itemIn.Name)) 18 { 19 //特别注意这里:SetValue和GetValue的用法 20 itemOut.SetValue(tOut, itemIn.GetValue(tIn)); 21 break; 22 } 23 } 24 } 25 return tOut; 26 } 27 #endregion 28 29 #region 封装-序列化反序列化进行实体见的赋值 30 /// <summary> 31 /// 序列化反序列化进行实体见的赋值 32 /// </summary> 33 /// <typeparam name="TIn">赋值的实体类型</typeparam> 34 /// <typeparam name="TOut">被赋值的实体类型</typeparam> 35 /// <param name="tIn"></param> 36 public static TOut SerialzerMapper<TIn, TOut>(TIn tIn) 37 { 38 return JsonConvert.DeserializeObject<TOut>(JsonConvert.SerializeObject(tIn)); 39 } 40 #endregion 41 42 #region 封装-字典缓存+表达式目录树 43 /// <summary> 44 /// 序列化反序列化进行实体见的赋值 45 /// </summary> 46 /// <typeparam name="TIn">赋值的实体类型</typeparam> 47 /// <typeparam name="TOut">被赋值的实体类型</typeparam> 48 /// <param name="tIn"></param> 49 public static TOut DicExpressionMapper<TIn, TOut>(TIn tIn) 50 { 51 string key = string.Format("funckey_{0}_{1}", typeof(TIn).FullName, typeof(TOut).FullName); 52 if (!_Dic.ContainsKey(key)) 53 { 54 ParameterExpression parameterExpression = Expression.Parameter(typeof(TIn), "p"); 55 List<MemberBinding> memberBindingList = new List<MemberBinding>(); 56 foreach (var item in typeof(TOut).GetProperties()) 57 { 58 MemberExpression property = Expression.Property(parameterExpression, typeof(TIn).GetProperty(item.Name)); 59 MemberBinding memberBinding = Expression.Bind(item, property); 60 memberBindingList.Add(memberBinding); 61 } 62 MemberInitExpression memberInitExpression = Expression.MemberInit(Expression.New(typeof(TOut)), memberBindingList.ToArray()); 63 Expression<Func<TIn, TOut>> lambda = Expression.Lambda<Func<TIn, TOut>>(memberInitExpression, new ParameterExpression[] 64 { 65 parameterExpression 66 }); 67 Func<TIn, TOut> func = lambda.Compile();//拼装是一次性的 68 _Dic[key] = func; 69 } 70 return ((Func<TIn, TOut>)_Dic[key]).Invoke(tIn); 71 } 72 #endregion
1 /// <summary> 2 /// 泛型缓存 3 /// </summary> 4 /// <typeparam name="TIn"></typeparam> 5 /// <typeparam name="TOut"></typeparam> 6 public class GenericExpressionMapper<TIn, TOut> 7 { 8 private static Func<TIn, TOut> _FUNC = null; 9 static GenericExpressionMapper() 10 { 11 ParameterExpression parameterExpression = Expression.Parameter(typeof(TIn), "p"); 12 List<MemberBinding> memberBindingList = new List<MemberBinding>(); 13 foreach (var item in typeof(TOut).GetProperties()) 14 { 15 MemberExpression property = Expression.Property(parameterExpression, typeof(TIn).GetProperty(item.Name)); 16 MemberBinding memberBinding = Expression.Bind(item, property); 17 memberBindingList.Add(memberBinding); 18 } 19 foreach (var item in typeof(TOut).GetFields()) 20 { 21 MemberExpression property = Expression.Field(parameterExpression, typeof(TIn).GetField(item.Name)); 22 MemberBinding memberBinding = Expression.Bind(item, property); 23 memberBindingList.Add(memberBinding); 24 } 25 MemberInitExpression memberInitExpression = Expression.MemberInit(Expression.New(typeof(TOut)), memberBindingList.ToArray()); 26 Expression<Func<TIn, TOut>> lambda = Expression.Lambda<Func<TIn, TOut>>(memberInitExpression, new ParameterExpression[] 27 { 28 parameterExpression 29 }); 30 _FUNC = lambda.Compile();//拼装是一次性的 31 } 32 public static TOut Trans(TIn t) 33 { 34 return _FUNC(t); 35 }