一. 基本介绍
回忆: 最早接触到表达式目录树(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;//利用反编译工具翻译下面这个ExpressionExpression<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 CopyUtils2 {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 = 314 };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 #endregion43 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 #endregion60 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 #endregion78 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 #endregion95 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 }
上述代码涉及到的几个封装
View Code
泛型缓存
最终运行结果:
三. 剥离表达式目录树
这里补充几个常用的表达式目录树的拼接代码:And、Or、Not 。
ExpressionExtend扩展类代码:
1 public static class ExpressionExtend2 {3 /// <summary>4 /// 合并表达式 expr1 AND expr25 /// </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 : ExpressionVisitor5 {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 VisitorUtils2 {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 }