欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Expression Trees--C#

程序员文章站 2022-07-14 14:38:46
...

https://docs.microsoft.com/en-us/dotnet/csharp/expression-trees

一,表达式树能做什么

  1)表达式树加强方法和参数的交互;

  2)表达式树可以在运行时操作代码,比如检查运行的算法,增加新的功能。

二,表达式树是什么

  以var sum = 1 + 2为例,该语句就可以看作表达式树:

  • 声明变量(var sum)
    • var关键字(var)
    • 变量名sum声明(sum)
  • 赋值操作符 (=)
  • 二进制加法表达式 (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;
            }
        }

   运行上述代码的话,出现无法访问已释放对象错误:

   Expression Trees--C#

四,解释表达式树

   一般来说,解释表达式树,从根节点开始,首先判断根节点的类型以及是否有子节点,然后递归遍历子节点,重复前面的过程。代码实例如下:

   定义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();
		}

得到的结果:

Expression Trees--C# 

五,构建表达式树

   构建表达式树是从叶子节点到根节点的,建立一个在运行时不可变的表达式树。

  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
    )
);

 六,改变表达式树

   最后一部分,可以了解到如何访问表达式树一个可修改的副本的每个节点。当改变一个表达式的时候,可以访问所有的节点,同时创建一个新的表达式树。