zoukankan      html  css  js  c++  java
  • Lambda&Expression Tree

    Lambda演变历史

    .Net Framework 1.0/1.1

    public class EvolutionHistory
    {
      public delegate void CustomDelegate(string param);
      
      public void Test()
      {
        CustomDelegate customDelegate = new CustomDelegate(DoWork);
        customDelegate.Invoke("test");
      }
      
      private void DoWork(string param)
      {
        Console.WriteLine(param);
      }
    }
    

    .Net Framework 2.0

    匿名方法,delegate关键字出现,简化了委托实例化参数

    public class EvolutionHistory
    {
      public delegate void CustomDelegate(string param);
      
      public void Test()
      {
        CustomDelegate customDelegate = new CustomDelegate(delegate (string param)
        {
          Console.WriteLine(param);
        });
        customDelegate.Invoke("test");
      }
    }
    

    .Net Framework 3.0

    Lambda表达式出现

    1. 去掉delegate关键字换成=>
    public class EvolutionHistory
    {
      public delegate void CustomDelegate(string param);
      
      public void Test()
      {
        CustomDelegate customDelegate = new CustomDelegate((string param) =>
        {
          Console.WriteLine(param);
        });
        customDelegate.Invoke("test");
      }
    }
    
    1. 编译器语法糖辅助,去掉参数类型,由委托参数推断
    public class EvolutionHistory
    {
      public delegate void CustomDelegate(string param);
      
      public void Test()
      {
        CustomDelegate customDelegate = new CustomDelegate(param =>
        {
          Console.WriteLine(param);
        });
        customDelegate.Invoke("test");
      }
    }
    
    1. 方法主体只有一行语句,可精简双括号
    public class EvolutionHistory
    {
      public delegate void CustomDelegate(string param);
      
      public void Test()
      {
        CustomDelegate customDelegate = new CustomDelegate(param =>
          Console.WriteLine(param)
        );
        customDelegate.Invoke("test");
      }
    }
    
    1. 编译器语法糖辅助,去掉实例化委托过程
    public class EvolutionHistory
    {
      public delegate void CustomDelegate(string param);
      
      public void Test()
      {
        CustomDelegate customDelegate = param => Console.WriteLine(param);
        customDelegate.Invoke("test");
      }
    }
    

    Lambda表达式本质

    Lambda表达式实际指定的部分,参数列表****=> 表达式或语句块;

    图片

    查看IL代码,存在一个私有密封类用于存储匿名方法,由编译器负责转换实现的

    图片

    Expression Tree

    表达式树是什么

    来回顾下数学中的二次函数表达式

    图片

    对其语义拆分后对应的树状结构

    图片

    用树状的数据结构来描述代码,便是表达式树(表达式可拆分成树状结构),提供数据存储,不用于计算。

    表达式树基类

    在C#中由Expression来定义,如上用表达式树构建,如下这种方式是基于Lambda表达式快速构建表达式树

    Expression<Func<int,int>> expression = x=>(x+1)*(x-2)
    

    查看下expression的信息,内部的Body中,Left和Right,不断的将表达式主体拆分细化,也就呈现了上图那样的树状结构,每一个节点都作为存储。
    图片

    表达式树构建

    Lambda构建表达式树

    用Lambda表达式构建表达式树,构建一个Lambda表达式树。但需注意的是该种方式不能使用语句块。

    Expression<Func<int,int>> expression = x=>(x+1)*(x-2);
    

    通过ILSpy可以查看下编译后的IL代码,可以看到编译器把Lambda表达式转换成了一堆的
    构建过程。图片

    此处得区分下Lambda表达式与Lambda表达式树的概念了。

    • Lambda表达式:x=>(x+1)*(x-2);
    • Lambda表达式树:用Lambda表达式声明表达式树便是如上构建的expression。

    组装构建表达式树

    除了用Lambda表达式快速构建表达式树,也可以一个节点一个节点构建,将所有节点组装起来构建一个Lambda表达式树。

    ParameterExpression param = Expression.Parameter(typeof(int), "x");
    BinaryExpression addExpression = Expression.Add(param, Expression.Constant(1));
    BinaryExpression subtractExpression = Expression.Subtract(param, Expression.Constant(2));
    BinaryExpression multiplyExpression = Expression.Multiply(addExpression, subtractExpression);
    Expression<Func<int, int>> expression = Expression.Lambda<Func<int, int>>(multiplyExpression, param);
    

    通过ILSpy查看下编译后的IL,可以看到对应的构建过程。
    图片

    表达式树构建方式区别

    用Lambda表达式构建表达式树通过借助编译器将该段Lambda表达式转换成表达式树的形式,通过ILSpy比对用组装构建的表达式树,两种是一样的。而具体哪种方式灵活,哪种方式扩展更强,就不言而喻了。

    组装表达式树中常用类型

    图片

    表达式树构建过程

    表达式树拼接过程是逐个节点的汇聚,依照中序遍历过程,以左中右,从底层节点从左往右构建起来。

    图片

    表达式树执行

    表达式树本身承担责任为存储,并不能直接执行,需要转换为委托实例执行

    Func<int, int> func = expression.Compile();
    func.Invoke(1);
    

    Expression Visitor

    解析表达式树是逆过程,反向拆分每一个节点,借助前序遍历,以中左右,从顶部中间节点往下遍历解析。

    图片

    Expression中提供了ExpressionVisitor类来解析该过程

    图片

    其中Visit方法主要职责是根据node类型,将其分配给具体的该节点类型处理方法,如同Switch般,将这个表达式分配到符合node类型的方法内。

    简单解析表达式树转Sql

    1. 先借助Lambda表达式构建表达式树
    Expression<Func<OrderItem, bool>> lambdaExpression = x =>
            x.Quantity > 5 && x.Quantity < 10 &&
            x.Name.StartsWith("a") &&
            x.Name.EndsWith("b") &&
            x.Name.Contains("c");
    
    
    1. 创建一个ConditionBuilderVisitor类并继承ExpressionVisitor,其中对几种类型进行方法
    public class ConditionBuilderVisitor : ExpressionVisitor
    {
        private readonly Stack<string> _stringStack = new Stack<string>();
        public ConditionBuilderVisitor(Expression expression)
        {
            Visit(expression);
            Condition();
        }
        public void Condition()
        {
            string condition = string.Concat(_stringStack.ToArray());
            _stringStack.Clear();
            Console.WriteLine(condition);
        }
        public override Expression Visit(Expression node)
        {
            Console.WriteLine($"Visit:{node.ToString()}");
            return base.Visit(node);
        }
    }
    
    1. 对二元表达式节点解析,将其存入栈内,对节点连接关系转换成Sql语言。
    protected override Expression VisitBinary(BinaryExpression node)
    {
        if (node == null) throw new ArgumentNullException("BinaryExpression");
        _stringStack.Push(")");
        base.Visit(node.Right);
        _stringStack.Push(" " + ToSqlOperator(node.NodeType) + " ");
        base.Visit(node.Left);
        _stringStack.Push("(");
        return node;
    }
    private string ToSqlOperator(ExpressionType type)
    {
        switch (type)
        {
            case (ExpressionType.AndAlso):
            case (ExpressionType.And):
                return "AND";
            case (ExpressionType.OrElse):
            case (ExpressionType.Or):
                return "OR";
            case (ExpressionType.Not):
                return "NOT";
            case (ExpressionType.NotEqual):
                return "<>";
            case ExpressionType.GreaterThan:
                return ">";
            case ExpressionType.GreaterThanOrEqual:
                return ">=";
            case ExpressionType.LessThan:
                return "<";
            case ExpressionType.LessThanOrEqual:
                return "<=";
            case (ExpressionType.Equal):
                return "=";
            default:
                throw new Exception("不支持该方法");
        }
    }
    
    1. 对为类中属性的节点解析,存入到栈中
    protected override Expression VisitMember(MemberExpression node)
    {
        if (node == null) throw new ArgumentNullException("MemberExpression");
        _stringStack.Push(" [" + node.Member.Name + "] ");
        return node;
    }
    
    1. 对为方法的节点解析,存入到栈中
    protected override Expression VisitMethodCall(MethodCallExpression m)
    {
        if (m == null) throw new ArgumentNullException("MethodCallExpression");
        string format;
        switch (m.Method.Name)
        {
            case "StartsWith":
                format = "({0} LIKE {1}+'%')";
                break;
            case "Contains":
                format = "({0} LIKE '%'+{1}+'%')";
                break;
            case "EndsWith":
                format = "({0} LIKE '%'+{1})";
                break;
            default:
                throw new NotSupportedException(m.NodeType + " is not supported!");
        }
        Visit(m.Object);
        Visit(m.Arguments[0]);
        string right = _stringStack.Pop();
        string left = _stringStack.Pop();
        _stringStack.Push(string.Format(format, left, right));
        return m;
    }
    
    1. 对为常量的的节点解析,存入栈中
    protected override Expression VisitConstant(ConstantExpression node)
    {
        if (node == null) throw new ArgumentNullException("ConstantExpression");
        _stringStack.Push(" '" + node.Value + "' ");
        return node;
    }
    
    1. 用Visitor去解析表达式树
    ConditionBuilderVisitor vistor = new ConditionBuilderVisitor(lambdaExpression);
    
    1. 这样一来,可以将表达式树转Sql输出了
      图片

    2021-02-27,望技术有成后能回来看见自己的脚步

  • 相关阅读:
    深入浅出理解基于 Kafka 和 ZooKeeper 的分布式消息队列
    消息队列使用的四种场景介绍
    《深入理解Java函数式编程》系列文章
    搭建微服务框架(Spring Boot + Dubbo + Docker + Jenkins)
    spring boot 整合dubbo
    IDEA中使用springBoot+gradle构建多模块项目
    Mac上zookeeper的安装与启动
    redis常用客户端命令
    mac下安装、配置redies
    轻松看懂机器学习常用算法
  • 原文地址:https://www.cnblogs.com/CKExp/p/14454940.html
Copyright © 2011-2022 走看看