Expression Trees--C#
https://docs.microsoft.com/en-us/dotnet/csharp/expression-trees
一,表达式树能做什么
1)表达式树加强方法和参数的交互;
2)表达式树可以在运行时操作代码,比如检查运行的算法,增加新的功能。
二,表达式树是什么
以var sum = 1 + 2为例,该语句就可以看作表达式树:
- 声明变量(
var sum
)- var关键字(
var
) - 变量名sum声明(
sum
)
- var关键字(
- 赋值操作符 (
=
) - 二进制加法表达式 (
1 + 2
)- 左操作数(
1
) - 加号 (
+
) - 右操作数 (
2
)
- 左操作数(
使用System.Linq.Expression库
,初次创建表达式树:
// Addition is an add expression for "1 + 2"
var one = Expression.Constant(1, typeof(int));
var two = Expression.Constant(2, typeof(int));
var addition = Expression.Add(one, two);
上述代码创建了两个叶子节点one和two。
另外,表达式树节点的类型和方法几乎包含了C#所有的语法类型和方法。
下面是表达式树的使用技巧:
1)使用ExpressionType来检查表达式树中可能的节点;
2)使用Expression类的静态方法来构建表达式;
3)使用ExpressionVIsitor类构建可修改的表达式。
三,执行表达式树
表达式树不是编译和可执行的代码,如果要执行它,需要将它转换成IL指令。
Expression<Func<int>> add = () => 1 + 2;
var func = add.Compile(); // Create Delegate
var answer = func(); // Invoke Delegate
Console.WriteLine(answer);
Expression<TDelegate>在表达式树中经常使用;在转换成IL中,它被转换成TDelegate委托,例如Func委托;在上述代码中实现这一功能的是Compile()方法。
注意问题,代码示例:
private static Func<int, int> CreateBoundFunc()
{
var constant = 5; // constant is captured by the expression tree
Expression<Func<int, int>> expression = (b) => constant + b;
var rVal = expression.Compile();
return rVal;
}
在上面代码中,constan是int值类型,所以不需要担心闭包问题,如果是个引用类型继承了IDispose接口,在垃圾回收机制回收后该方法将会继续持有这个变量,将会引发异常,如下所示:
public class Resource : IDisposable
{
private bool isDisposed = false;
public int Argument
{
get
{
if (!isDisposed)
return 5;
else throw new ObjectDisposedException("Resource");
}
}
public void Dispose()
{
isDisposed = true;
}
}
private static Func<int, int> CreateBoundResource()
{
using (var constant = new Resource()) // constant is captured by the expression tree
{
Expression<Func<int, int>> expression = (b) => constant.Argument + b;
var rVal = expression.Compile();
return rVal;
}
}
运行上述代码的话,出现无法访问已释放对象错误:
四,解释表达式树
一般来说,解释表达式树,从根节点开始,首先判断根节点的类型以及是否有子节点,然后递归遍历子节点,重复前面的过程。代码实例如下:
定义LambdaExpression等几个类,用来打印lambda表达式各节点。
using System;
using System.Linq.Expressions;
namespace ConsoleApp1
{
// Base Visitor class:
public abstract class Visitor
{
private readonly Expression node;
protected Visitor(Expression node)
{
this.node = node;
}
public abstract void Visit(string prefix);
public ExpressionType NodeType => this.node.NodeType;
public static Visitor CreateFromExpression(Expression node)
{
switch (node.NodeType)
{
//a constant value
case ExpressionType.Constant:
return new ConstantVisitor((ConstantExpression)node);
//a lambda expression
case ExpressionType.Lambda:
return new LambdaVisitor((LambdaExpression)node);
//a reference of parameter or variable
case ExpressionType.Parameter:
return new ParameterVisitor((ParameterExpression)node);
//a addtion operation
case ExpressionType.Add:
return new BinaryVisitor((BinaryExpression)node);
default:
Console.Error.WriteLine($"Node not processed yet: {node.NodeType}");
return default(Visitor);
}
}
}
// Lambda Visitor
public class LambdaVisitor : Visitor
{
private readonly LambdaExpression node;
public LambdaVisitor(LambdaExpression node) : base(node)
{
this.node = node;
}
/// <summary>
/// a recursion to visit expression all node
/// </summary>
/// <param name="prefix"></param>
public override void Visit(string prefix)
{
Console.WriteLine($"{prefix}This expression is a {NodeType} expression type");
Console.WriteLine($"{prefix}The name of the lambda is {((node.Name == null) ? "<null>" : node.Name)}");
Console.WriteLine($"{prefix}The return type is {node.ReturnType.ToString()}");
Console.WriteLine($"{prefix}The expression has {node.Parameters.Count} argument(s). They are:");
// Visit each parameter:
foreach (var argumentExpression in node.Parameters)
{
var argumentVisitor = Visitor.CreateFromExpression(argumentExpression);
argumentVisitor.Visit(prefix + "\t");
}
Console.WriteLine($"{prefix}The expression body is:");
// Visit the body:
var bodyVisitor = Visitor.CreateFromExpression(node.Body);
bodyVisitor.Visit(prefix + "\t");
}
}
// Binary Expression Visitor:
public class BinaryVisitor : Visitor
{
private readonly BinaryExpression node;
public BinaryVisitor(BinaryExpression node) : base(node)
{
this.node = node;
}
public override void Visit(string prefix)
{
Console.WriteLine($"{prefix}This binary expression is a {NodeType} expression");
var left = Visitor.CreateFromExpression(node.Left);
Console.WriteLine($"{prefix}The Left argument is:");
left.Visit(prefix + "\t");
var right = Visitor.CreateFromExpression(node.Right);
Console.WriteLine($"{prefix}The Right argument is:");
right.Visit(prefix + "\t");
}
}
// Parameter visitor:
public class ParameterVisitor : Visitor
{
private readonly ParameterExpression node;
public ParameterVisitor(ParameterExpression node) : base(node)
{
this.node = node;
}
public override void Visit(string prefix)
{
Console.WriteLine($"{prefix}This is an {NodeType} expression type");
Console.WriteLine($"{prefix}Type: {node.Type.ToString()}, Name: {node.Name}, ByRef: {node.IsByRef}");
}
}
// Constant visitor:
public class ConstantVisitor : Visitor
{
private readonly ConstantExpression node;
public ConstantVisitor(ConstantExpression node) : base(node)
{
this.node = node;
}
public override void Visit(string prefix)
{
Console.WriteLine($"{prefix}This is an {NodeType} expression type");
Console.WriteLine($"{prefix}The type of the constant value is {node.Type}");
Console.WriteLine($"{prefix}The value of the constant value is {node.Value}");
}
}
}
在main函数中执行:
static void Main(string[] args)
{
Expression<Func<int, int, int>> addtion = (a, b) => a + b;
var recu = new LambdaVisitor(addtion);
recu.Visit("");
Console.ReadLine();
}
得到的结果:
五,构建表达式树
构建表达式树是从叶子节点到根节点的,建立一个在运行时不可变的表达式树。
1)创建node
例如:
Expression<Func<int>> sum = () => 1 + 2;
上面表达式的叶子节点是两个常量,我们需要创建两个constant节点,然后创建addtion的lambda表达式,代码如下:
var lambda = Expression.Lambda(
Expression.Add(
Expression.Constant(1, typeof(int)),
Expression.Constant(2, typeof(int))
)
);
2)创建复杂的tree
例如下面的lambda表达式,我们可以在运行时构建:
Expression<Func<double, double, double>> distanceCalc =
(x, y) => Math.Sqrt(x * x + y * y);
创建Parameter表达式x和y:
var xParameter = Expression.Parameter(typeof(double), "x");
var yParameter = Expression.Parameter(typeof(double), "y");
创建乘法和加法表达式:
var xSquared = Expression.Multiply(xParameter, xParameter);
var ySquared = Expression.Multiply(yParameter, yParameter);
var sum = Expression.Add(xSquared, ySquared);
创建可以调用Math.Sqrt方法的表达式:
var sqrtMethod = typeof(Math).GetMethod("Sqrt", new[] { typeof(double) });
var distance = Expression.Call(sqrtMethod, sum);
最后创建lambda表达式:
var distanceLambda = Expression.Lambda(
distance,
xParameter,
yParameter);
从上面的例子,可以看出,创建一个tree我们需要从叶子节点开始构建,然后逐级上推,直至根节点lambda表达式。这是一个自下而上的过程,其中遇到的函数调用,这里使用了MethodInfo的反射机制
。
接下来分析一个更复杂的表达式,lambda求阶乘值如下:
Func<int, int> factorialFunc = (n) =>
{
var res = 1;
while (n > 1)
{
res = res * n;
n--;
}
return res;
};
上面这个例子,很难用一般的方式来构建表达式树,因为没有while循环的api来调用;因此为了达到目的,创建一个loop,然后使用label功能跳出循环,代码如下:
var nArgument = Expression.Parameter(typeof(int), "n");
var result = Expression.Variable(typeof(int), "result");
// Creating a label that represents the return value
LabelTarget label = Expression.Label(typeof(int));
var initializeResult = Expression.Assign(result, Expression.Constant(1));
// This is the inner block that performs the multiplication,
// and decrements the value of 'n'
var block = Expression.Block(
Expression.Assign(result,
Expression.Multiply(result, nArgument)),
Expression.PostDecrementAssign(nArgument)
);
// Creating a method body.
BlockExpression body = Expression.Block(
new[] { result },
initializeResult,
Expression.Loop(
Expression.IfThenElse(
Expression.GreaterThan(nArgument, Expression.Constant(1)),
block,
Expression.Break(label, result)
),
label
)
);
六,改变表达式树
最后一部分,可以了解到如何访问表达式树一个可修改的副本的每个节点。当改变一个表达式的时候,可以访问所有的节点,同时创建一个新的表达式树。
推荐阅读
-
C#条件拼接Expression<Func<T, bool>>的使用
-
[Err] 1055 - Expression #1 of ORDER BY clause is not in GROUP BY clause 的问题 MySQL
-
[BZOJ1385] [Baltic2000] Division expression
-
Python使用正则表达式(Regular Expression)超详细
-
解决大于5.7版本mysql的分组报错Expression #1 of SELECT list is not in GROUP BY clause and contains nonaggregated
-
EL(Expression Language)
-
浅谈正则表达式(Regular Expression)
-
详解SqlServer 表达式(expression)
-
ae怎么添加expression表达式? ae中expression的用法
-
解决大于5.7版本mysql的分组报错Expression #1 of SELECT list is not in GROUP BY clause and contains nonaggregated column 'userinfo.