第十五节: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 }
最终运行结果:
三. 剥离表达式目录树
这里补充几个常用的表达式目录树的拼接代码:And、Or、Not 。
ExpressionExtend扩展类代码:
1 public static class ExpressionExtend 2 { 3 /// <summary> 4 /// 合并表达式 expr1 AND expr2 5 /// </summary> 6 /// <typeparam name="T"></typeparam> 7 /// <param name="expr1"></param> 8 /// <param name="expr2"></param> 9 /// <returns></returns> 10 public static Expression<Func<T, bool>> And<T>(this Expression<Func<T, bool>> expr1, Expression<Func<T, bool>> expr2) 11 { 12 ParameterExpression newParameter = Expression.Parameter(typeof(T), "c"); 13 NewExpressionVisitor visitor = new NewExpressionVisitor(newParameter); 14 15 var left = visitor.Replace(expr1.Body); 16 var right = visitor.Replace(expr2.Body); 17 var body = Expression.And(left, right); 18 return Expression.Lambda<Func<T, bool>>(body, newParameter); 19 20 } 21 /// <summary> 22 /// 合并表达式 expr1 or expr2 23 /// </summary> 24 /// <typeparam name="T"></typeparam> 25 /// <param name="expr1"></param> 26 /// <param name="expr2"></param> 27 /// <returns></returns> 28 public static Expression<Func<T, bool>> Or<T>(this Expression<Func<T, bool>> expr1, Expression<Func<T, bool>> expr2) 29 { 30 31 ParameterExpression newParameter = Expression.Parameter(typeof(T), "c"); 32 NewExpressionVisitor visitor = new NewExpressionVisitor(newParameter); 33 34 var left = visitor.Replace(expr1.Body); 35 var right = visitor.Replace(expr2.Body); 36 var body = Expression.Or(left, right); 37 return Expression.Lambda<Func<T, bool>>(body, newParameter); 38 } 39 public static Expression<Func<T, bool>> Not<T>(this Expression<Func<T, bool>> expr) 40 { 41 var candidateExpr = expr.Parameters[0]; 42 var body = Expression.Not(expr.Body); 43 44 return Expression.Lambda<Func<T, bool>>(body, candidateExpr); 45 } 46 }
NewExpressionVisitor建立新表达式辅助类代码:
1 /// <summary> 2 /// 建立新表达式 3 /// </summary> 4 internal class NewExpressionVisitor : ExpressionVisitor 5 { 6 public ParameterExpression _NewParameter { get; private set; } 7 public NewExpressionVisitor(ParameterExpression param) 8 { 9 this._NewParameter = param; 10 } 11 public Expression Replace(Expression exp) 12 { 13 return this.Visit(exp); 14 } 15 protected override Expression VisitParameter(ParameterExpression node) 16 { 17 return this._NewParameter; 18 } 19 }
如何使用的代码:
1 public class VisitorUtils 2 { 3 public static void Show() 4 { 5 Expression<Func<User, bool>> lambda1 = x => x.userAge > 5; 6 Expression<Func<User, bool>> lambda2 = x => x.id > 5; 7 Expression<Func<User, bool>> lambda3 = lambda1.And(lambda2); 8 Expression<Func<User, bool>> lambda4 = lambda1.Or(lambda2); 9 Expression<Func<User, bool>> lambda5 = lambda1.Not(); 10 11 Do1(lambda1); 12 Do1(lambda2); 13 Do1(lambda3); 14 Do1(lambda4); 15 Do1(lambda5); 16 } 17 18 private static void Do1(Expression<Func<User, bool>> func) 19 { 20 List<User> user = new List<User>() 21 { 22 new User(){id=4,userName="123",userAge=4}, 23 new User(){id=5,userName="234",userAge=5}, 24 new User(){id=6,userName="345",userAge=6}, 25 }; 26 27 List<User> peopleList = user.Where(func.Compile()).ToList(); 28 } 29 }
!
- 作 者 : Yaopengfei(姚鹏飞)
- 博客地址 : http://www.cnblogs.com/yaopengfei/
- 声 明1 : 如有错误,欢迎讨论,请勿谩骂^_^。
- 声 明2 : 原创博客请在转载时保留原文链接或在文章开头加上本人博客地址,否则保留追究法律责任的权利。