zoukankan      html  css  js  c++  java
  • C#表达式树浅析

    一、前言

         在我们日常开发中Lamba 表达式经常会使用,如List.Where(n=>Name="abc") 使用起来非常的方便,代码也很简洁,总之一个字就是“爽”。在之前我们总是用硬编码的方式去实现一些底层方法,比如我要查询用户“abc”是否存在,老的实现方式基本为:GetUser(string name) ,可能下次我需要按用户登录账号来查询,于是又得写一个方法:GetUserByLogin(string loginCode),我们认真想一下,如果能实现类似于集合查询那样只要写lambda 就能搞定,List.Where(n=>Name="abc"),List.Where(n=>LoginCode=="小A"),有了这样的想法,那我们也去了解一下lambda 表达式树的背后吧。

    二、初识

          表达式树关键字“Expressions”,我们在官方文档里面可以看到很多介绍,具体信息请查看微软官方文档库;官方文档里面的信息量比较大,有几十个对象的介绍:

    这里我不建议大家从头到尾的看一遍,大致浏览就好了,因为信息量太多了。首先我们新建一个控制台程序,框架版本选FX4.0以上或者Core 都行,引入命名空间:

    using System.Linq.Expressions; 接下来实现一个简单的功能,解析表达式: n=>n.Name="abc" 我们想要的结果是 Name="abc",有了这个目标我们就知道该干嘛了。

    定义函数:private static string GetLambdaStr<T>(Expression<Func<T, bool>> expression),该函数定义了一个表达式树参数,Func<in T,out bool>是范型委托,该委托表示接收一个T类型参数,返回一个bool值。具体代码:

            private static string GetLambdaStr<T>(Expression<Func<T, bool>> expression)
            {
                //解析表达式
                //return new Analysis().AnalysisExpression(expression);
    return null; }

     接下来建立一个用户对象:

            public class User
            {
                public int ID { get; set; }
                public string Name { get; set; }
                public int Age { get; set; }
                public bool States { get; set; }
            }

    再建立好测试代码:

            //Expression<Func<T, bool>> lambda = n => n.Name == "abc";
                Console.WriteLine("lambda :  n => n.Name == "abc" ");
                var a = GetLambdaStr<User>(n => n.Name == "abc");
                Console.WriteLine("result:" + a);
                Console.Write(Environment.NewLine);
                Console.ReadKey();

    先不管那么多,我们调试进去看看表达式长啥样:

    这样看比较清真,就是一个lambda表达式,我们展开看看对象明细:

     

    看到这里我们是不是能想起点什么了,这其实就是一颗二叉树,显示的层次为根节点,左子节点,右子节点,依次循环。有了这个认知,我们立马能想到可以使用递归来遍历节点了。

         于是我来了解表达式对象“Expression”有哪些属性和方法:

    看到这里有点困惑了,刚刚我们明明看到有Left、Right 属性,但这里却没有,感觉好坑呀。没有左右节点我们根本不知道怎么去递归查找子节点呢。于是又去看官方介绍文档了,然后再仔细看了LambdaExpression 对象,这个是抽象类,有抽象必定有相关的实现或者提供对外属性,仔细一找,刚好找到BinaryExpression对象,有Left、Right属性同时继承了Expression对象,也提供了LambdaExpression 属性,这个就是我们要找的对象了,可以说是峰回路转了:

     顺着这个思路,我又找到了属性成员和属性值对象MemberExpression、ConstantExpression,我们来实现关键代码

            private static string GetLambdaStr<T>(Expression<Func<T, bool>> expression)
            {
                //解析表达式
                var body = (BinaryExpression)expression.Body;
                var r = (ConstantExpression)body.Right;
                var l = (MemberExpression)body.Left;
                var value = r.Type.IsValueType ? r.Value : $"'{r.Value}'";
                return $"{ l.Member.Name} {Operand(body.NodeType)} {value} ";
                // return new Analysis().AnalysisExpression(expression);
            }

    Operand 是操作类型转换,代码如下:

            //操作符转换
            private string Operand(ExpressionType type)
            {
                string operand = string.Empty;
                switch (type)
                {
                    case ExpressionType.AndAlso:
                        operand = "AND";
                        break;
                    case ExpressionType.OrElse:
                        operand = "OR";
                        break;
                    case ExpressionType.Equal:
                        operand = "=";
                        break;
                    case ExpressionType.LessThan:
                        operand = "<";
                        break;
                    case ExpressionType.LessThanOrEqual:
                        operand = "<=";
                        break;
                    case ExpressionType.GreaterThan:
                        operand = ">";
                        break;
                    case ExpressionType.GreaterThanOrEqual:
                        operand = ">";
                        break;
                }
                return operand;
            }
    View Code

    有了上面的代码我们已经完成功能了,运行结果如下:

    三、进阶

         日常开发中我们遇到的查询条件可能会更加复杂,于是我又写了几个复杂得表达式:

                //Expression<Func<T, bool>> lambda = n => n.states;
                Console.WriteLine("analysis: n => n.states ");
                var b = GetLambdaStr<User>(n => n.States);
                Console.WriteLine("result:" + b);
                Console.ReadKey();
    
                //Expression<Func<T, bool>> lambda = n => n.Name == "abc" && n.Age > 30 || n.ID == 4;
                Console.WriteLine("lambda: n => n.Name == "abc" && n.Age > 30 || n.ID == 4");
                var c = GetLambdaStr<User>(n => n.Name == "abc" && (n.Age > 30 || n.ID == 4) && n.ID > 1
    && (n.ID > 19 || n.Name == "33")); Console.WriteLine("result:" + c); Console.Write(Environment.NewLine); Console.ReadKey();

     经过我的一番探索和调试,终于完成了解析:

    建议手动去敲一遍代码,并调试,这中间我遇到了一些坑,比如使用了OR条件时需要增加括号,这个括号老是没放对位置。

    最后贴出全部代码:

    1、控制台代码

                //Expression<Func<T, bool>> lambda = n => n.Name == "abc";
                Console.WriteLine("lambda :  n => n.Name == "abc" ");
                var a = GetLambdaStr<User>(n => n.Name == "abc");
                Console.WriteLine("result:" + a);
                Console.Write(Environment.NewLine);
                Console.ReadKey();
    
                //Expression<Func<T, bool>> lambda = n => n.states;
                Console.WriteLine("analysis: n => n.states ");
                var b = GetLambdaStr<User>(n => n.States);
                Console.WriteLine("result:" + b);
                Console.ReadKey();
    
                //Expression<Func<T, bool>> lambda = n => n.Name == "abc" && n.Age > 30 || n.ID == 4;
                Console.WriteLine("lambda: n => n.Name == "abc" && n.Age > 30 || n.ID == 4");
                var c = GetLambdaStr<User>(n => n.Name == "abc" && (n.Age > 30 || n.ID == 4) && n.ID > 1 && (n.ID > 19 || n.Name == "33"));
                Console.WriteLine("result:" + c);
                Console.Write(Environment.NewLine);
                Console.ReadKey();
    View Code

    2、解析函数

            private static string GetLambdaStr<T>(Expression<Func<T, bool>> expression)
            {
                //解析表达式
                return new Analysis().AnalysisExpression(expression);
            }
    View Code

    3、用户对象代码上面已经有了就不重复发了

    4、解析对象代码

        public class Analysis
        {
            private StringBuilder builder = new StringBuilder();
            public string AnalysisExpression<TDelegate>(Expression<TDelegate> expression)
            {
                if (expression.Body is MemberExpression)
                {
                    var m = (MemberExpression)expression.Body;
                    var value = Convert.ToInt32(!expression.Body.ToString().Contains("!"));
                    builder.Append($"  ({m.Member.Name}={value}) ");
                    return builder.ToString();
                }
                var body = (BinaryExpression)expression.Body;
                if (body.NodeType == ExpressionType.AndAlso || body.NodeType == ExpressionType.OrElse)
                {
                    AnalysisExpressionChild((BinaryExpression)body.Left, body.NodeType);
                    AnalysisExpressionChild((BinaryExpression)body.Right, body.NodeType);
                }
                else
                {
                    var r = (ConstantExpression)body.Right;
                    var l = (MemberExpression)body.Left;
                    var value = r.Type.IsValueType ? r.Value : $"'{r.Value}'";
                    builder.Append($" { l.Member.Name} {Operand(body.NodeType)} {value} ");
                }
                return builder.ToString();
            }
    
            //解析表达式树
            private void AnalysisExpressionChild(BinaryExpression expression, ExpressionType pType, string brackets = "")
            {
                if (expression.NodeType != ExpressionType.AndAlso && expression.NodeType != ExpressionType.OrElse)
                {
                    var r = (ConstantExpression)expression.Right;
                    var l = (MemberExpression)expression.Left;
                    var value = r.Type.IsValueType ? r.Value : $"'{r.Value}'";
                    builder.Append($" {Operand(pType)} {brackets} { l.Member.Name} {Operand(expression.NodeType)} {value} ");
                }
                else
                {
                    if (expression.NodeType == ExpressionType.OrElse)
                    {
                        brackets = "( ";
                    }
                    AnalysisExpressionChild((BinaryExpression)expression.Left, pType, brackets);
                    AnalysisExpressionChild((BinaryExpression)expression.Right, expression.NodeType);
                    if (expression.NodeType == ExpressionType.OrElse)
                    {
                        builder.Append(" )");
                    }
                }
            }
    
            //操作符转换
            private string Operand(ExpressionType type)
            {
                string operand = string.Empty;
                switch (type)
                {
                    case ExpressionType.AndAlso:
                        operand = "AND";
                        break;
                    case ExpressionType.OrElse:
                        operand = "OR";
                        break;
                    case ExpressionType.Equal:
                        operand = "=";
                        break;
                    case ExpressionType.LessThan:
                        operand = "<";
                        break;
                    case ExpressionType.LessThanOrEqual:
                        operand = "<=";
                        break;
                    case ExpressionType.GreaterThan:
                        operand = ">";
                        break;
                    case ExpressionType.GreaterThanOrEqual:
                        operand = ">";
                        break;
                }
                return operand;
            }
    
        }
    View Code

     至此,表达式树已经完成了解析,上面的案例已经能满足常用的需求了,若有其他要求我们可以继续改造拓展解析方法。

    四、总结

          我们在学习技术的时候带着一定的目的去学习往往效率更高,又不容易忘记,同时要善于思考,联系上下文情景。如果你觉得看完后对你有帮助可以给我点赞。

    相关代码已经放到GitHub 

    参考文档:

    1、微软官方文档库:https://docs.microsoft.com/zh-cn/dotnet/api/system.linq.expressions?view=netcore-2.2

    2、腾讯云:https://cloud.tencent.com/developer/article/1334993

    五、补充

         经过继续研究和分析网友的解析方法,发现其实微软对表达式解析已经提供的了一个专门类:ExpressionVisitor,该类实现了对各种表达式操作的解析,我们直接继承它, 所以我又重写了一个解析类:AnalyseExpressionHelper,需要修改案例中调用解析方法的代码:

            private static string GetLambdaStr<T>(Expression<Func<T, bool>> expression)
            {
                //解析表达式三
                var heler = new AnalyseExpressionHelper();
                heler.AnalyseExpression(expression);
                return heler.Result;
            }

    操作符转换函数已经修改为拓展方法:

     
    using System.Linq.Expressions;
    
    
    namespace ExpressionTreeDemo
    {
        public static class ExpressionExtend
        {
            //操作符转换
            public static string TransferOperand(this ExpressionType type)
            {
                string operand = string.Empty;
                switch (type)
                {
                    case ExpressionType.AndAlso:
                        operand = "AND";
                        break;
                    case ExpressionType.OrElse:
                        operand = "OR";
                        break;
                    case ExpressionType.Equal:
                        operand = "=";
                        break;
                    case ExpressionType.NotEqual:
                        operand = "<>";
                        break;
                    case ExpressionType.LessThan:
                        operand = "<";
                        break;
                    case ExpressionType.LessThanOrEqual:
                        operand = "<=";
                        break;
                    case ExpressionType.GreaterThan:
                        operand = ">";
                        break;
                    case ExpressionType.GreaterThanOrEqual:
                        operand = ">=";
                        break;
                }
                return operand;
            }
        }
    }
    ExpressionExtend

    新的表达式解析方法:

    using System;
    using System.Text;
    using System.Linq.Expressions;
    
    namespace ExpressionTreeDemo
    {
        /// <summary>
        /// 表达式解析辅助类
        /// </summary>
        public class AnalyseExpressionHelper : ExpressionVisitor
        {
            private StringBuilder express = new StringBuilder();
            public string Result { get { return express.ToString(); } }
    
            public void AnalyseExpression<T>(Expression<Func<T, bool>> expression)
            {
                Visit(expression.Body);
            }
    
            protected override Expression VisitBinary(BinaryExpression node)
            {
                if (node.NodeType == ExpressionType.OrElse)
                    express.Append("(");
                Visit(node.Left);
                express.Append($" {node.NodeType.TransferOperand()} ");
                Visit(node.Right);
                if (node.NodeType == ExpressionType.OrElse)
                    express.Append(")");
                return node;
            }
    
            protected override Expression VisitConstant(ConstantExpression node)
            {
                if (node.Type.IsValueType && node.Type != typeof(DateTime))
                {
                    express.Append(node.Value);
                }
                else
                {
                    express.Append($"'{node.Value}'");
                }
                return node;
            }
    
            protected override Expression VisitMember(MemberExpression node)
            {
                express.Append(node.Member.Name);
                return node;
            }
        }
    }
    AnalyseExpressionHelper
    本人专注于.net平台开发,擅长开发企业管理系统,CRM系统,ERP系统,财务系统,权限系统,非常乐意跟大家讨论相关系统的设计和开发技巧
  • 相关阅读:
    springboot整合shiro
    四大作用域:application,session,request,page
    Application作用域实现:当用户重复登录时,挤掉原来的用户
    从Linux下载大于4G文件到本地,并且在本地合并
    idea+maven+springboot+mybatis
    Spring 3.0 中一般 普通类调用service
    java微信扫码支付Native(模式二)
    阿里云不支持stmp 的25端口,必须
    python写入文本报错TypeError: expected a string or other character buffer object
    mysql找到数据的存储位置
  • 原文地址:https://www.cnblogs.com/heweijian/p/11406715.html
Copyright © 2011-2022 走看看