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,望技术有成后能回来看见自己的脚步

posted @ 2021-02-27 10:35  微笑刺客D  阅读(171)  评论(1编辑  收藏  举报
返回顶部