【C#表达式树 开篇】 Expression Tree - 动态语言
阅读原文时间:2023年07月11日阅读:2

.NET 3.5中新增的表达式树(Expression Tree)特性,第一次在.NET平台中引入了“逻辑即数据”的概念。也就是说,我们可以在代码里使用高级语言的形式编写一段逻辑,但是这段逻辑最终会被保存为数据。正因为如此,我们可以使用各种不同的方法对它进行处理。例如,您可以将其转化为一个SQL查询,或者外部服务调用等等,这便是LINQ to Everything在技术实现上的重要基石之一。

学习表达式目录树的目的:

1、之所以学习表达式树的相关知识点主要是为了能进一步理解linq to sql相关执行原理

2、表达式树还能用于动态语言运行时 (DLR) 以提供动态语言和 .NET Framework 之间的互操作性。
3、表达式具有缓存性,可以用与替代反射

在学习表达式目录树前我们要回顾一下知识点:

【C# 基础概念】表达式(expression)、语句(statement)、块(block),指令(using)

任何表达式都有值和类型两个基本属性

表达式的每个节点都是表达式,所以即使是常量都要封装成几点,然后添加的树中

进入正题。。。。。。。。。。。。

表达式树是什么?

表达式树事一个二叉树,c#引入表达式树,用来储存动态代码,因此C#中的表达式树是一种数据结构,这个数据结构用来存储代码。

我们把它当成另外一种动态语言学习就好了,它也有常量、参数、运算、判断等操作。

下一节我们介绍到expression类的时候就就会接触到它各种函数。

表达式树是不可变对象(immutable),跟string类似,不能直接修改,只能复制一个然后重新构造。具体参考MSDN How to modify expression trees (C#).

表达式树的创建有 Lambda法 和 API组装法

表达式树 (C#)

表达式树以树形数据结构表示代码,其中每一个节点都是一种表达式,比如方法调用和 x < y 这样的二元运算等。

你可以对表达式树中的代码进行编辑和运算。 这样能够动态修改可执行代码、在不同数据库中执行 LINQ 查询以及创建动态查询。 有关 LINQ 中表达式树的详细信息,请参阅如何使用表达式树生成动态查询 (C#)

表达式树还能用于动态语言运行时 (DLR) 以提供动态语言和 .NET 之间的互操作性,同时保证编译器编写员能够发射表达式树而非 Microsoft 中间语言 (MSIL)。 有关 DLR 的详细信息,请参阅动态语言运行时概述

你可以基于匿名 lambda 表达式通过 C# 或者 Visual Basic 编译器创建表达式树,或者通过 System.Linq.Expressions 名称空间手动创建。

  • Body: 得到表达式的主体。
  • Parameters: 得到lambda表达式的参数.
  • NodeType: 获取树的节点的ExpressionType。共85种不同值,包含所有表达式节点各种可能的类型,例如返回常量,例如返回参数,例如取两个值的小值(<),例如取两个值的大值(>),例如将值相加(+),等等。
  • Type: 获取表达式的一个静态类型。在这个例子里,表达式的类型是Func<intintint>。

1、根据 Lambda 表达式创建表达式树

若 lambda 表达式被分配给 Expression 类型的变量,则编译器可以发射代码以创建表示该 lambda 表达式的表达式树。

下列代码示例展示如何通过 C# 编译器创建表示 Lambda 表达式 num => num < 5 的表达式树。

Expression> lambda = num => num < 5;

2、通过 API 创建表达式树

通过 API 创建表达式树需要使用 Expression 类。 类包含创建特定类型表达式树节点的静态工厂方法,比如表示参数变量的 ParameterExpression,或者是表示方法调用的 MethodCallExpressionParameterExpression 名称空间还解释了 MethodCallExpressionSystem.Linq.Expressions和另一种具体表达式类型。 这些类型来源于抽象类型 Expression

下列代码示例展示如何使用 API 创建表示 Lambda 表达式 num => num < 5 的表达式树。

// Add the following using directive to your code file:
// using System.Linq.Expressions;

// Manually build the expression tree for
// the lambda expression num => num < 5. ParameterExpression numParam = Expression.Parameter(typeof(int), "num"); ConstantExpression five = Expression.Constant(5, typeof(int)); BinaryExpression numLessThanFive = Expression.LessThan(numParam, five); Expression> lambda1 =
Expression.Lambda>(
numLessThanFive,
new ParameterExpression[] { numParam });

在 .NET Framework 4 或更高版本中,表达式树 API 还支持赋值表达式和控制流表达式,例如循环、条件块和 try-catch 块等。 相对于通过 C# 编译器和 Lambda 表达式创建表达式树,还可利用 API 创建更加复杂的表达式树。 下列示例展示如何创建计算数字阶乘的表达式树。

// Creating a parameter expression.
ParameterExpression value = Expression.Parameter(typeof(int), "value");

// Creating an expression to hold a local variable.
ParameterExpression result = Expression.Parameter(typeof(int), "result");

// Creating a label to jump to from a loop.
LabelTarget label = Expression.Label(typeof(int));

// Creating a method body.
BlockExpression block = Expression.Block(
// Adding a local variable.
new[] { result },
// Assigning a constant to a local variable: result = 1
Expression.Assign(result, Expression.Constant(1)),
// Adding a loop.
Expression.Loop(
// Adding a conditional block into the loop.
Expression.IfThenElse(
// Condition: value > 1
Expression.GreaterThan(value, Expression.Constant(1)),
// If true: result *= value --
Expression.MultiplyAssign(result,
Expression.PostDecrementAssign(value)),
// If false, exit the loop and go to the label.
Expression.Break(label, result)
),
// Label to jump to.
label
)
);

// Compile and execute an expression tree.
int factorial = Expression.Lambda>(block, value).Compile()(5);

Console.WriteLine(factorial);
// Prints 120.

表达式树应具有永久性。 这意味着如果你想修改某个表达式树,则必须复制该表达式树然后替换其中的节点来创建一个新的表达式树。 你可以使用表达式树访问者遍历现有表达式树。 有关详细信息,请参阅如何修改表达式树 (C#)

C# 编译器只能从表达式 Lambda(或单行 Lambda)生成表达式树。 它无法解析语句 lambda (或多行 lambda)。 有关 C# 中 Lambda 表达式的详细信息,请参阅 Lambda 表达式

表达式树 编译后会生成委托

//创建表达式的语法糖
Expression> expr = num => num < 5;

// Compiling the expression tree into a delegate.
Func result = expr.Compile();

// Invoking the delegate and writing the result to the console.
Console.WriteLine(result(4));

//语法糖,编译后执行
Console.WriteLine(expr.Compile()(4));

注意:public Delegate Compile();
LambdaExpression.Compile() 方法返回 Delegate 类型。 必须将其转换为正确的委托类型,以便使任何编译时工具检查参数列表或返回类型。

public sealed class Expression
{
public TDelegate Compile(){}
}
Expression> add = () => 1 + 2;
var func = add.Compile(); // Create Delegate
该委托类型基于表达式类型。 如果想要以强类型的方式使用委托对象,则必须知道返回类型和参数列表。

通过调用在调用 LambdaExpression.Compile() 时创建的委托来执行代码。 可以在上面进行查看,其中 add.Compile() 返回了一个委托。 通过调用 func() 调用该委托将执行代码。

该委托表示表达式树中的代码。 可以保留该委托的句柄并在稍后调用它。 不需要在每次想要执行表达式树所表示的代码时编译表达式树。 (请记住,表达式树是不可变的,且在之后编译同一表达式树将创建执行相同代码的委托。)

注意闭包问题,要保证被lambda捕获的局部变量,在在表达式树生成的的委托 是都可用

https://docs.microsoft.com/zh-cn/dotnet/csharp/expression-trees-execution

Expressions 表达式的命名空间

Expression 表达式抽象基类通常做继承

Expression=>LambdaExpression=>Expression=>Object 用来接收LambdaExpression.ComPile 返回值。

ExpressionType:表达式树的节点类型枚举

在这里插入图片描述
当然还有没有直接继承Expression的,比如:Expression(我们最常用的表达式树)的继承关系为:Expression=>LambdaExpression=>Expression=>Object,这里我就挑选几个表达式树简单描述一下:

    BinaryExpression:二元运算表达式
      1). Right:二元运算符的右侧表达式(Expression)
      2). Left:二元运算符的左侧表达式(Expression)
    ConstantExpression:常量表达式
      1). Value:常量值,Object
    ConditionalExpression:条件表达式
      1). IfFalse:为False时的表达式(Expression)
      2). IfTrue:为True时的表达式(Expression)
      3). Test:判断表达式
    ParameterExpression:参数表达式
      1). IsByRef:参数是否传引用
      2). Name:参数名称
    LambdaExpression:lambda表达式
      1).Body:内容表达式(Expression)
      2).Parameters:参数表达式集合(ReadOnlyCollection)
      3).ReturnType:返回的类型
      4).Compile():方法,编译生成委托
    Expression:带有泛型的表达式,一般这个T就是委托类型的,继承自LabelExpression