|
Csharper中的表达式树
这节课来了解一下表示式树是什么?
在C#中,表达式树是一种数据结构,它可以表示一些代码块,如Lambda表达式或查询表达式。表达式树使你能够查看和操作数据,就像你可以查看和操作代码一样。它们通常用于创建动态查询和解析表达式。
一、认识表达式树
为什么要这样说?它和委托有什么区别?
创建一个简单的表达式树和委托- public class ExpressionDemo
- {
- void Show()
- {
- Func<int, bool> fun1 = x => x > 10;
- Expression<Func<int, bool>> expression1 = x => x > 10;
- }
- }
复制代码 然后f12转到定义- public sealed class Expression<TDelegate> : LambdaExpression
复制代码 尝试用大括号定义一个表达式树
debug运行后,用vs查看一下定义的表达式树对象.
发现表达式树一些特点:
- 可以通过lambda表达式来声明
- 是一个泛型类的接口,类型参数是一个委托
- Expression声明中,不能包含大括号.
- 通过VS展开查看,包含body(lamubda的主体部分),描述了参数的名称和类型,描述了返回值的名称和类型; 展开body, body包含 左边是什么,右边是什么,式子的操作类型是什么.
结论:
表达式树,是一个计算式的描述,按照常规的计算逻辑,通过类的属性来进行描述多个节点之间的关系; 形似于一个树形结构----二叉树; 二叉树不断地去分解,可以得到这个式子中的任何一个独立的元素;----是一个二叉树,是一个数据结构; 如果需要可以把这个结构不断的拆解;得到中间的最小元素;在需要的时候,也可以通过每个元素,组装起来;
委托是一个类,而表达式树是一个二叉树的数据结构。
为了更加深入的了解表达式树,这里也使用ilspy进行反编译,以便于更加了解表达式树的本质.
这里使用一个比较复杂的表达式树的语句来方便我们去理解- Expression<Func<int ,int ,int>> expression2= (x, y) => x *y+2+3;
复制代码
优化一下这段代码- //定义2个变量
- ParameterExpression parameterExpression = Expression.Parameter(typeof(int), "x");
- ParameterExpression parameterExpression2 = Expression.Parameter(typeof(int), "y");
- //定义常量
- var contact1 = Expression.Constant(2, typeof(int));
- var contact2= Expression.Constant(3, typeof(int));
- //定义表达式 x*y
- var MultiplyXy= Expression.Multiply(parameterExpression, parameterExpression2);
- //定义表达式 x*y的结果+2
- var add1 = Expression.Add(MultiplyXy, contact1);
- //定义表达式 x*y+2的结果+3
- var add2 = Expression.Add(add1, contact2);
- //定义最终的lambda表达式
- Expression<Func<int, int, int>> expression2 = Expression.Lambda<Func<int, int, int>>(add2, new ParameterExpression[2]
- {
- parameterExpression,
- parameterExpression2
- });
复制代码 如图所示的解析:
已经将相应的代码粘贴到上方,就是类似二叉树结构的因式分解,转换成为最小的子问题,最后解决一个需要解决的大问题。
二、动态拼装Expression
我们自己去拼装一个表达式树去理解表达式树的秘密.
首先创建一个People类- public class People
- {
- public int Age { get; set; }
- public string Name { get; set; }
- public int Id;
- }
复制代码 下面来拼接一个比较复杂的表达式- Expression<Func<People, bool>> predicate = c => c.Id == 10 && c.Name.ToString().Equals("张三");
复制代码 对应的表达式树的代码- //定义一个People类型的参数
- ParameterExpression parameterExpression = Expression.Parameter(typeof(People), "c");
- //获取People的Id属性
- PropertyInfo? propertyId = typeof(People).GetProperty("Id");
- //定义10这个常量
- ConstantExpression constantExpression = Expression.Constant(10, typeof(int));
- //定义c.Id>10这个表达式
- BinaryExpression left =Expression.GreaterThan(Expression.Property(parameterExpression, propertyId), constantExpression);
- //获取People的Name属性
- PropertyInfo? propertyName = typeof(People).GetProperty("Name");
- //c.Name
- MemberExpression memName = Expression.Property(parameterExpression, propertyName);
- //to string方法
- MethodInfo? methodtostring=typeof(string).GetMethod("ToString",new Type[0]);
- //调用tostring方法
- MethodCallExpression instance =Expression.Call(memName, methodtostring,Array.Empty<Expression>());
- //获取equals方法
- MethodInfo? methodEquals = typeof(string).GetMethod("Equals", new Type[] { typeof(string) });
- //定义c.Name.ToString().Equals("张三")这个表达式
- MethodCallExpression right = Expression.Call(instance, methodEquals, Expression.Constant("张三", typeof(string)));
- //定义c.Age<25这个表达式
- PropertyInfo? propertyAge = typeof(People).GetProperty("Age");
- ConstantExpression constantExpression2 = Expression.Constant(25, typeof(int));
- BinaryExpression right2 = Expression.LessThan(Expression.Property(parameterExpression, propertyAge), constantExpression2);
- //定义c.Id>10 && c.Name.ToString().Equals("张三") && c.Age<25这个表达式
- BinaryExpression and1 = Expression.AndAlso(left, right);
- BinaryExpression and2 = Expression.AndAlso(and1, right2);
- //定义最终的lambda表达式
- Expression<Func<People, bool>> expression = Expression.Lambda<Func<People, bool>>(and2, new ParameterExpression[1]
- {
- parameterExpression
- });
- //编译表达式
- Func<People, bool> func = expression.Compile();
- //调用表达式
- People people = new People()
- {
- Id = 11,
- Name = "张三",
- Age = 20
- };
- Console.WriteLine(func(people));
复制代码 拼接后的结果- Expression<Func<People, bool>> expression2 = p => p.Id == 10 && p.Name.Equals("阳光下的微笑");
复制代码 例子2- //按关键字是否存在来拼装;
- Expression<Func<People, bool>> exp = p=> true;
- Console.WriteLine("用户输入个名称,为空就跳过");
- string name = Console.ReadLine();
- if (!string.IsNullOrWhiteSpace(name))
- {
- //exp = p => p.Name.Contains(name);
- exp= exp.And(c=>c.Name.Contains(name));
- }
- Console.WriteLine("用户输入个最小年纪,为空就跳过");
- string age = Console.ReadLine();
- if (!string.IsNullOrWhiteSpace(age) && int.TryParse(age, out int iAge))
- {
- // exp = p => p.Age > iAge;
- exp = exp.And(p => p.Age > iAge);
- }
复制代码 现在使用表达式树进行链接- Expression lambda1 = x => x.Age > 5; Expression lambda2 = x => x.Id > 5; //按关键字是否存在来拼装;
- Expression<Func<People, bool>> exp = p=> true;
- Console.WriteLine("用户输入个名称,为空就跳过");
- string name = Console.ReadLine();
- if (!string.IsNullOrWhiteSpace(name))
- {
- //exp = p => p.Name.Contains(name);
- exp= exp.And(c=>c.Name.Contains(name));
- }
- Console.WriteLine("用户输入个最小年纪,为空就跳过");
- string age = Console.ReadLine();
- if (!string.IsNullOrWhiteSpace(age) && int.TryParse(age, out int iAge))
- {
- // exp = p => p.Age > iAge;
- exp = exp.And(p => p.Age > iAge);
- }; Expression lambda3 = lambda1.And(lambda2); //且 两个都满足,通过&&链接 Expression lambda4 = lambda1.Or(lambda2);//或 两个只要有一个就可以 通过或者来链接 || Expression lambda5 = lambda1.Not();//非
复制代码 这里实现了常见的且、或、非逻辑运算符的表达式- Expression<Func<People, bool>> lambda1 = x => x.Age > 5;
- Expression<Func<People, bool>> lambda2 = x => x.Id > 5;
- //Expression<Func<People, bool>> newExpress = x => x.Age > 5 && x.Id > 5;
- Expression<Func<People, bool>> lambda3 = lambda1.And(lambda2); //且 两个都满足,通过&&链接
- Expression<Func<People, bool>> lambda4 = lambda1.Or(lambda2);//或 两个只要有一个就可以 通过或者来链接 ||
- Expression<Func<People, bool>> lambda5 = lambda1.Not();//非
复制代码 现在有一个新的需求,需要把People拷贝到NewPeople这个新的类,来看下效率怎么样?
People和PeopleCopy类- public static class ExpressionExtend
- {
- /// <summary>
- /// 合并表达式 expr1 AND expr2
- /// </summary>
- /// <typeparam name="T"></typeparam>
- /// <param name="expr1"></param>
- /// <param name="expr2"></param>
- /// <returns></returns>
- public static Expression<Func<T, bool>> And<T>(this Expression<Func<T, bool>> expr1, Expression<Func<T, bool>> expr2)
- {
- //return Expression.Lambda<Func<T, bool>>(Expression.AndAlso(expr1.Body, expr2.Body), expr1.Parameters);
- ParameterExpression newParameter = Expression.Parameter(typeof(T), "c");
- NewExpressionVisitor visitor = new NewExpressionVisitor(newParameter);
- var left = visitor.Replace(expr1.Body);
- var right = visitor.Replace(expr2.Body); //为了能够生成一个新的表达式目录树
- var body = Expression.And(left, right);
- return Expression.Lambda<Func<T, bool>>(body, newParameter);
- }
- /// <summary>
- /// 合并表达式 expr1 or expr2
- /// </summary>
- /// <typeparam name="T"></typeparam>
- /// <param name="expr1"></param>
- /// <param name="expr2"></param>
- /// <returns></returns>
- public static Expression<Func<T, bool>> Or<T>(this Expression<Func<T, bool>> expr1, Expression<Func<T, bool>> expr2)
- {
- ParameterExpression newParameter = Expression.Parameter(typeof(T), "c");
- NewExpressionVisitor visitor = new NewExpressionVisitor(newParameter);
- var left = visitor.Replace(expr1.Body);
- var right = visitor.Replace(expr2.Body);
- var body = Expression.Or(left, right);
- return Expression.Lambda<Func<T, bool>>(body, newParameter);
- }
- public static Expression<Func<T, bool>> Not<T>(this Expression<Func<T, bool>> expr)
- {
- var candidateExpr = expr.Parameters[0];
- var body = Expression.Not(expr.Body);
- return Expression.Lambda<Func<T, bool>>(body, candidateExpr);
- }
- }
- internal class NewExpressionVisitor : ExpressionVisitor
- {
- public ParameterExpression _NewParameter { get; private set; }
- public NewExpressionVisitor(ParameterExpression param)
- {
- this._NewParameter = param;
- }
- public Expression Replace(Expression exp)
- {
- return this.Visit(exp);
- }
- protected override Expression VisitParameter(ParameterExpression node)
- {
- return this._NewParameter;
- }
- }
复制代码 直接赋值的方式- public class People
- {
- public int Age { get; set; }
- public string Name { get; set; }
- public int Id;
- }
- /// <summary>
- /// 实体类Target
- /// PeopleDTO
- /// </summary>
- public class PeopleCopy
- {
- public int Age { get; set; }
- public string Name { get; set; }
- public int Id;
- }
复制代码 反射赋值的方式- PeopleCopy peopleCopy1 = new PeopleCopy()
- {
- Id = people.Id,
- Name = people.Name,
- Age = people.Age
- };
复制代码 json序列化的方式- public class ReflectionMapper
- {
- /// <summary>
- /// 反射
- /// </summary>
- /// <typeparam name="TIn"></typeparam>
- /// <typeparam name="TOut"></typeparam>
- /// <param name="tIn"></param>
- /// <returns></returns>
- public static TOut Trans<TIn, TOut>(TIn tIn)
- {
- TOut tOut = Activator.CreateInstance<TOut>();
- foreach (var itemOut in tOut.GetType().GetProperties())
- {
- var propName = tIn.GetType().GetProperty(itemOut.Name);
- itemOut.SetValue(tOut, propName.GetValue(tIn));
- }
- foreach (var itemOut in tOut.GetType().GetFields())
- {
- var fieldName = tIn.GetType().GetField(itemOut.Name);
- itemOut.SetValue(tOut, fieldName.GetValue(tIn));
- }
- return tOut;
- }
- }
- PeopleCopy peopleCopy2= ReflectionMapper.Trans<People, PeopleCopy>(people);
复制代码 表达式目录树的方式- public class SerializeMapper
- {
- /// <summary>
- /// 序列化反序列化方式
- /// </summary>
- /// <typeparam name="TIn"></typeparam>
- /// <typeparam name="TOut"></typeparam>
- public static TOut Trans<TIn, TOut>(TIn tIn)
- {
- string strTin = JsonConvert.SerializeObject(tIn);
- return JsonConvert.DeserializeObject<TOut>(strTin);
- }
- }
- PeopleCopy peopleCopy3 = SerializeMapper.Trans<People, PeopleCopy>(people);
复制代码 表达式+反射+泛型类的方式- public class ExpressionMapper
- {
- /// <summary>
- /// 字典缓存--hash分布
- /// </summary>
- private static Dictionary<string, object> _Dic = new Dictionary<string, object>();
- /// <summary>
- /// 字典缓存表达式树
- /// </summary>
- /// <typeparam name="TIn"></typeparam>
- /// <typeparam name="TOut"></typeparam>
- /// <param name="tIn"></param>
- /// <returns></returns>
- public static TOut Trans<TIn, TOut>(TIn tIn)
- {
- string key = string.Format("funckey_{0}_{1}", typeof(TIn).FullName, typeof(TOut).FullName);
- if (!_Dic.ContainsKey(key))
- {
- #region 这里是拼装---赋属性值的代码
- ParameterExpression parameterExpression = Expression.Parameter(typeof(TIn), "p");
- //MemberBinding: 就是一个表达式目录树
- List<MemberBinding> memberBindingList = new List<MemberBinding>();
- foreach (var item in typeof(TOut).GetProperties()) //这里是处理属性的
- {
- MemberExpression property = Expression.Property(parameterExpression, typeof(TIn).GetProperty(item.Name));
- MemberBinding memberBinding = Expression.Bind(item, property);
- memberBindingList.Add(memberBinding);
- }
- foreach (var item in typeof(TOut).GetFields()) //处理字段的
- {
- MemberExpression property = Expression.Field(parameterExpression, typeof(TIn).GetField(item.Name));
- MemberBinding memberBinding = Expression.Bind(item, property);
- memberBindingList.Add(memberBinding);
- }
- MemberInitExpression memberInitExpression = Expression.MemberInit(Expression.New(typeof(TOut)), memberBindingList.ToArray()); //组装了一个转换的过程;
- Expression<Func<TIn, TOut>> lambda = Expression.Lambda<Func<TIn, TOut>>(memberInitExpression, new ParameterExpression[]
- {
- parameterExpression
- });
- #endregion
- Func<TIn, TOut> func = lambda.Compile();//拼装是一次性的
- _Dic[key] = func;
- }
- return ((Func<TIn, TOut>)_Dic[key]).Invoke(tIn);
- }
- }
- PeopleCopy peopleCopy4 = ExpressionMapper.Trans<People, PeopleCopy>(people);
复制代码 最后运行一百万次,来看一下效率。- public class ExpressionGenericMapper<TIn, TOut>//Mapper`2
- {
- private static Func<TIn, TOut> _FUNC = null;
- static ExpressionGenericMapper()
- {
- ParameterExpression parameterExpression = Expression.Parameter(typeof(TIn), "p");
- List<MemberBinding> memberBindingList = new List<MemberBinding>();
- foreach (var item in typeof(TOut).GetProperties())
- {
- MemberExpression property = Expression.Property(parameterExpression, typeof(TIn).GetProperty(item.Name));
- MemberBinding memberBinding = Expression.Bind(item, property);
- memberBindingList.Add(memberBinding);
- }
- foreach (var item in typeof(TOut).GetFields())
- {
- MemberExpression property = Expression.Field(parameterExpression, typeof(TIn).GetField(item.Name));
- MemberBinding memberBinding = Expression.Bind(item, property);
- memberBindingList.Add(memberBinding);
- }
- MemberInitExpression memberInitExpression = Expression.MemberInit(Expression.New(typeof(TOut)), memberBindingList.ToArray());
- Expression<Func<TIn, TOut>> lambda = Expression.Lambda<Func<TIn, TOut>>(memberInitExpression, new ParameterExpression[]
- {
- parameterExpression
- });
- _FUNC = lambda.Compile();//拼装是一次性的
- }
- public static TOut Trans(TIn t)
- {
- return _FUNC(t);
- }
- }
- }
- PeopleCopy peopleCopy5 = ExpressionGenericMapper<People, PeopleCopy>.Trans(people);
复制代码 看运行后的结果
核心:动态生成硬编码;----代码运行的时候生成了一段新的逻辑;
四、表达式树和sql
为什么要使用表达式目录树来拼装解析呢?
可以提供重用性
如果封装好一个方法,接受一个表达式树,在解析的时候,其实就是不断的访问,访问的时候,会按照固定的规则,避免出错;
任何的一个表达式树都可以用一个通用的方法解析并且支持泛型,更加容易去封装;
例子:
需要的扩展类- {
- People people = new People()
- {
- Id = 11,
- Name = "Richard",
- Age = 31
- };
- long common = 0;
- long generic = 0;
- long cache = 0;
- long reflection = 0;
- long serialize = 0;
- {
- Stopwatch watch = new Stopwatch();
- watch.Start();
- for (int i = 0; i < 1_000_000; i++)
- {
- PeopleCopy peopleCopy = new PeopleCopy()
- {
- Id = people.Id,
- Name = people.Name,
- Age = people.Age
- };
- }
- watch.Stop();
- common = watch.ElapsedMilliseconds;
- }
- {
- Stopwatch watch = new Stopwatch();
- watch.Start();
- for (int i = 0; i < 1_000_000; i++)
- {
- PeopleCopy peopleCopy = ReflectionMapper.Trans<People, PeopleCopy>(people);
- }
- watch.Stop();
- reflection = watch.ElapsedMilliseconds;
- }
- {
- Stopwatch watch = new Stopwatch();
- watch.Start();
- for (int i = 0; i < 1_000_000; i++)
- {
- PeopleCopy peopleCopy = SerializeMapper.Trans<People, PeopleCopy>(people);
- }
- watch.Stop();
- serialize = watch.ElapsedMilliseconds;
- }
- {
- Stopwatch watch = new Stopwatch();
- watch.Start();
- for (int i = 0; i < 1_000_000; i++)
- {
- PeopleCopy peopleCopy = ExpressionMapper.Trans<People, PeopleCopy>(people);
- }
- watch.Stop();
- cache = watch.ElapsedMilliseconds;
- }
- {
- Stopwatch watch = new Stopwatch();
- watch.Start();
- for (int i = 0; i < 1_000_000; i++)
- {
- PeopleCopy peopleCopy = ExpressionGenericMapper<People, PeopleCopy>.Trans(people);
- }
- watch.Stop();
- generic = watch.ElapsedMilliseconds;
- }
- Console.WriteLine($"common = {common} ms"); //性能最高,但是不能通用;
- Console.WriteLine($"reflection = {reflection} ms");
- Console.WriteLine($"serialize = {serialize} ms");
- Console.WriteLine($"cache = {cache} ms");
- Console.WriteLine($"generic = {generic} ms"); //性能好,而且扩展性也好===又要马儿跑,又要马儿不吃草。。。
- }
复制代码 对应的表达式解析- public class OperationsVisitor : ExpressionVisitor
- {
- public Expression Modify(Expression expression)
- {
- Console.WriteLine(expression.ToString()) ;
- //ExpressionVisitor:
- //1.Visit方法--访问表达式目录树的入口---分辨是什么类型的表达式目录
- //2.调度到更加专业的方法中进一步访问,访问一遍之后,生成一个新的表达式目录 ---有点像递归,不全是递归;
- //3.因为表达式目录树是个二叉树,ExpressionVisitor一直往下访问,一直到叶节点;那就访问了所有的节点
- //4.在访问的任何一个环节,都可以拿到对应当前环节的内容(参数名称、参数值。。),就可以进一步扩展
- return this.Visit(expression);
- }
- /// <summary>
- /// 覆写父类方法
- /// </summary>
- /// <param name="b"></param>
- /// <returns></returns>
- protected override Expression VisitBinary(BinaryExpression b)
- {
-
- if (b.NodeType == ExpressionType.Add)
- {
- Expression left = this.Visit(b.Left);
- Expression right = this.Visit(b.Right);
- return Expression.Subtract(left, right);
- }
- else if (b.NodeType==ExpressionType.Multiply) //如果是相乘
- {
- Expression left = this.Visit(b.Left);
- Expression right = this.Visit(b.Right);
- return Expression.Divide(left, right); //相除
- }
- return base.VisitBinary(b);
- }
- /// <summary>
- /// 覆写父类方法
- /// </summary>
- /// <param name="node"></param>
- /// <returns></returns>
- protected override Expression VisitConstant(ConstantExpression node)
- {
- return base.VisitConstant(node);
- }
复制代码 同时表达式树中已经通过使用观察者模式封装好了Visit方法.
- Visit方法--访问表达式树的入口---分辨是什么类型的表达式目录
- 调度到更加专业的方法中进一步访问,访问一边以后,生成一个新的表达式目录. --- 有点像递归,不全是递归
- 因为表达式目录树是一个二叉树,ExpreesionVistor一直往下访问,一直到叶子节点;通过二叉树的遍历就访问了所有的节点.
- 在访问的任何一个环节,都可以拿到对应当前环节的内容(参数名称、参数值...)就可以进一步扩展.
现在开始将表达式树跟sql语句进行连接
例子:
扩展类- Expression<Func<int, int, int>> exp = (m, n) => m * n + 2;
- OperationsVisitor visitor = new OperationsVisitor();
- //visitor.Visit(exp);
- Expression expNew = visitor.Modify(exp);
复制代码 对应的sql语句的解析在我自己的看法,使用表达式树而不是传统的方式去解析sql语句的优点
- 通过二叉树的方式表达,更加的有条理性
- 使用泛型等技术更方式实现一个通用的sql语句的解析。
- 会有类型检查,出错后也能使用异常处理。
来源:https://www.cnblogs.com/wenlong-4613615/p/18084777
免责声明:由于采集信息均来自互联网,如果侵犯了您的权益,请联系我们【E-Mail:cb@itdo.tech】 我们会及时删除侵权内容,谢谢合作! |
本帖子中包含更多资源
您需要 登录 才可以下载或查看,没有账号?立即注册
x
|