C#的表达式树(System.Linq.Expressions)

概述

在C#编程中,表达式树(Expression)是一种数据结构,表达式树也称表达式目录树,是将代码以一种抽象的方式表示成一个对象树,树中每个节点本身都是一个表达式。表达式树不是可执行代码,它是一种数据结构。但是可以利用LambdaExpression类的派生类Expression<TDelegate>表达式树转化成可执行的Lambda表达式的委托,当然也可以将Lambda表达式转化成表达式树

表达式树将表达式抽象成树状结构,每个节点代表表达式中的一个元素,如常量、变量、方法调用等。这种结构使得表达式易于分析和转换,同时也为动态生成代码和进行运行时分析提供了便利。

扩展:为什么要将表达式抽象成树形结构呢?

答:涉及到的应用,其中一个应用为表达式解析,我们可以将表达式表示为树结构,叶节点保存操作数,内部节点保存操作符表达式层次决定计算的优先级,越底层的表达式,优先级越高。如图中优先计算(7+3)(5-2)再进行乘运算。其每一个子树都是一个表达式,子树计算后就成了一个节点。
20201029194740784.png

表达式树可以用于诸如解析XMLJSON数据LINQ查询数据绑定反射还是其他领域,表达式树都是一个非常有用的工具。总结一下就是通常在写框架或底层时需要用到,日常场景上一般用不到。

// 创建表达式树:Lambda法
Expression<Func<int, int, int>> add = (x, y) => x + y;
Func<int, int, int> func = add.Compile();
func.Invoke(10, 20); // 输出:30

// 创建表达式树:组装法
// 创建一个 ParameterExpression 节点,该节点可用于标识表达式树中的参数或变量。
ParameterExpression x = Expression.Parameter(typeof(int), "x");
ParameterExpression y = Expression.Parameter(typeof(int), "y");
// 创建一个表示不进行溢出检查的算术加法运算的 BinaryExpression
BinaryExpression add = Expression.Add(x, y);
// 构造一个委托类型以及一个参数表达式的数组,创建 LambdaExpression。
Expression<Func<int, int, int>> addEx = Expression.Lambda<Func<int, int, int>>(add, new ParameterExpression[2]{
    x, y
});
// 使用Compile该方法将表达式树重新转换为可执行代码
Func<int, int, int> func = addEx.Compile();
// 执行委托
func.Invoke(10, 20); // 输出:30

表达式树基础

官方文档:https://learn.microsoft.com/zh-cn/dotnet/api/system.linq.expressions?view=net-7.0
节点类型:https://learn.microsoft.com/zh-cn/dotnet/api/system.linq.expressions.expressiontype?view=net-6.0

在C#中,我们可以使用System.Linq.Expressions命名空间下的Expression类来创建表达式树。其中每一个类都表示表达式树中的节点。而Expression类包含static工厂方法来创建各种类型的表达式树节点

通俗的来讲,System.Linq.Expressions中的类大部分是表示表达式树中的节点,这些节点都是由Expression静态方法()创建而来,例如上面的ParameterExpression x = Expression.Parameter(typeof(int), "x");,且在查阅该命名空间下的节点类时发现节点类没有构造方法

需要注意官方文档中的节点类型, 是表达式的基础, 每一个节点类型代表一种基础操作(在Expression类的方法中实现), 掌握了这些类型的用法后表达式树就算入门了。

常用类:Expression类

这是一个抽象类, 前面也说了,非常重要,所有的节点类型都由其派生而来。特别是其提供的static方法用以创建各种节点类型,下面会经常使用这个类提供的static方法

常用类:Expression<TDelegate>类

以表达式树的形式将强类型lambda表达式表示为数据结构。 此类不能被继承。强类型lambda表达式是明确指定了返回类型和参数类型的lambda表达式。例如:(int x, int y) => x + y 是一个强类型的 lambda 表达式。

Expression<TDelegate> 类是 C# 中用于将强类型的lambda表达式转换为表达式树的一个工具,也就是如果你将一个lambda表达式赋值给这个类对象,那么lambda将以表达式目录树的形式存在内存中。

Expression<Func<int, bool>> exp = a => a > 10;
// 或者使用 Expression.lambda()生成
var x = Expression.Parameter(typeof(int), "x");
var inc = Expression.GreaterThan(x, Expression.Constant(10));
Expression<Func<int, bool>> exp = Expression.Lambda<Func<int, bool>>(inc, new ParameterExpression[] {x});

注意:Expression<TDelegate>对象是可以作为参数传递给方法的。

LambdaExpression:lambda表达式

类型 LambdaExpression 以表达式树的形式表示 lambda 表达式。在上例中,我们使用Expression.Lambda<Func<int, bool>>(inc, new ParameterExpression[] {x})创建了一个Expression<TDelegate>,而它其实是由LambdaExpression类派生而来。

这个类中有几个重要的属性和方法:

  • body: lambda表达式的主体。
  • Parameters:获取lambda表达式的参数。
  • ReturnType:获取lambda表达式的返回类型。
  • Compile(): 生成表示lambda表达式的委托。

变量/常量/默认值

// ParameterExpression类(参数表达式)
ParameterExpression parameter1 = Expression.Parameter(typeof(int),"m");
ParameterExpression parameter2 = Expression.Variable(typeof(String), "sampleVar");

// ConstantExpression类(参数表达式)
ConstantExpression constant1= Expression.Constant(123,typeof(int));

// DefaultExpression类(默认值)
DefaultExpression default1 = Expression.Default(typeof(int))

基本运算

// UnaryExpression类(一元运算)
// BinaryExpression类(二元运算)加减乘除等一些基本运算都算是二元运算

// 赋值操作 =
ParameterExpression parameter1 = Expression.Parameter(typeof(int),"m");
BinaryExpression assign = Expression.Assign(parameter1, Expression.Constant(8));

// ===== 基础运算:加、减、乘、除、取模 =====
// +与带边界检查的+,边界检查就是超出范围了会抛出异常
BinaryExpression test = Expression.Add(Expression.Constant(x), Expression.Constant(y))
BinaryExpression test = Expression.AddChecked(Expression.Constant(x), Expression.Constant(y))
// += 与 带边界检查的+=
var paraTmp = Expression.Parameter(typeof(int), "i");
BinaryExpression test = Expression.AddAssign(paraTmp, Expression.Constant(1));
Expression.AddAssignChecked(paraTmp, Expression.Constant(1);
// 减-、-=
Expression.Subtract(); // 两个操作数相减;
Expression.SubtractChecked(); // 两个操作数相减,并检查计算是否溢出;
Expression.SubtractAssign(); // 两个数相减并将结果赋值给第一个数;
Expression.SubtractAssignChecked(); // 两个数相减并将结果赋值给第一个数,并检查是否溢出;
// 乘*、*=
Expression.Multiply(); // 两个操作数相乘;
Expression.MultiplyChecked(); // 两个操作数相乘,并检查计算是否溢出;
Expression.MultiplyAssign(); // 两个数相乘并将结果赋值给第一个数;
Expression.MultiplyAssignChecked(); // 两个数相乘并将结果赋值给第一个数,并检查是否溢出;
// 除/、/=
Expression.Divide(); // 两个操作数相除;
Expression.DivideAssign(); // 两个数相除并将结果赋值给第一个数
// 取模%、%=
Expression.Modulo(); // 两个操作数相除取余;
Expression.ModuloAssign(); // 两个数相除取余并将结果赋值给第一个数;

// ===== 位运算、关系运算: =====
// &、&&、&=
Expression.And(); // 两个操作数位运算或逻辑and;
Expression.AndAlso(); // 逻辑运算,短路and;
Expression.AndAssign(); // 两按位或逻辑 AND 复合赋值运算,如 C# 中的 (a &= b);
// |、||、|=
Expression.Or(); // 两个操作数位运算或逻辑or;
Expression.OrElse(); // 短路条件 OR 运算,如 C# 中的 (a || b) 或 Visual Basic 中的 (a OrElse b)。
Expression.OrAssign(); // 按位或逻辑 OR 复合赋值运算,如 C# 中的 (a |= b)。
// !、!=、==
Expression.Not(); // 按位求补运算或逻辑求反运算。 在 C# 中,它与整型的 (~a) 和布尔值的 (!a) 等效。
Expression.NotEqual(); // 不相等比较,如 C# 中的 (a != b) 。
Expression.Equal(); // 表示相等比较的节点,如 C# 中的 (a == b) 。
// >、>=
Expression.GreaterThan(); // “大于”比较,如 (a > b)。
Expression.GreaterThanOrEqual(); // “大于或等于”比较,如 (a >= b)。
// <、<=
Expression.LessThan(); // “小于”比较,如 (a < b)。
Expression.LessThanOrEqual(); // “小于或等于”比较,如 (a <= b)

// 逻辑 XOR 运算:a^b,a^=b
Expression.ExclusiveOr(); // 按位或逻辑 XOR 运算,如 C# 中的 (a ^ b) 和 Visual Basic 中的 (a Xor b)。
Expression.ExclusiveOrAssign(); // 按位或逻辑 XOR 复合赋值运算,如 c # 中 的 (^ = b) 。
// 移位运算:a>>b,a>>=b
Expression.RightShift(); // 按位右移运算,如 (a >> b)。
Expression.RightShiftAssign(); // 按位右移复合赋值运算,如 (a >>= b)。
// 移位运算:a<<b,a<<=b
Expression.LeftShift(); // 按位左移运算,如 (a << b)。
Expression.LeftShiftAssign(); // 按位左移运算,如 (a << b)。


// ===== 递增与递减运算: =====
// ++a、a++
Expression.PreIncrementAssign(); // 一元前缀递增,如 (++a)。 应就地修改 a 对象。
Expression.PostIncrementAssign(); // 一元后缀递增,如 (a++)。 应就地修改 a 对象。
// --a、a--
Expression.PreDecrementAssign(); // 一元前缀递减,如 (–a)。 应就地修改 a 对象。
Expression.PostDecrementAssign(); // 一元后缀递减,如 (a–)。 应就地修改 a 对象
// 增加或减一:Decrement(i),Increment(i),比较特殊
Expression.Decrement(); // 表示按 1 递减表达式值。
Expression.Increment(); // 表示按 1 递增表达式值。
// 算数求反:-a,checked(-a)
Expression.Negate(); // 算术求反运算,如 (-a)。 不应就地修改 a 对象。
Expression.NegateChecked(); // 算术求反运算,如 (-a),进行溢出检查。 不应就地修改 a 对象。
// 一元加法:+a
Expression.UnaryPlus(); // 一元加法运算,如 (+a)。 预定义的一元加法运算的结果是操作数的值,但用户定义的实现可以产生特殊结果。

// ===== 其他: =====
Expression.Power(); // 幂运算 Math.Pow(2,4)=16
Expression.PowerAssign(); // 幂运算后赋值

// null 合并运算:a??b
ExpressionType.Coalesce(); // 表示 null 合并运算的节点,如 C# 中的 (a ?? b)。
Expression.OnesComplement(Expression); // 返回表示一的补数的表达式

Expression.MakeBinary(); // 通过调用适当的工厂方法来创建一个 BinaryExpression, 比如2-1,1*2等都可以
Expression.MakeUnary(); // 在给定操作数的情况下,通过调用适当的工厂方法来创建一个 UnaryExpression。

条件、分支运算

// 三元运算符:x>y?x:y
Expression.Condition(); // 条件运算,如 C# 中的 a > b ? a : b
// if、else
Expression.IfThen(条件表达式, IfTrue); 
Expression.IfThenElse(条件表达式, IfTrue, IfFalse);
// switch
Expression.Switch(); // 多分支选择运算,如 C# 中的 switch。
Expression.SwitchCase(); // 创建在 SwitchCase 中使用的 SwitchExpression。

循环运算

Expression.Break(); // 创建一个表示 break 语句的 GotoExpression。
Expression.Continue(); // 创建一个表示 continue 语句
Expression.Loop(); // 一个循环,例如 for 或 while。
Expression.Goto(); // 创建一个表示“go to”语句的 GotoExpression。
Expression.MakeGoto(); // 创建一个 GotoExpression
Expression.Label(); // 创建一个标签,主要用于在goto中使用

//for (var i = 0; i < 5;i++)
//{
//    Console.WriteLine(i);
//}
//Console.WriteLine("end loop");
// 实现如下:
var parai = Expression.Parameter(typeof(int), "i");
var breakLabel = Expression.Label("break");
var continueLabel = Expression.Label("continue");

var loopInit = Expression.Assign(parai, Expression.Constant(0));//i=0
var loopExp = Expression.Loop(
     Expression.Block(
         Expression.IfThenElse(//if
             Expression.LessThan(parai, Expression.Constant(5)),//i<5
             Expression.Block(//then
                Expression.Call(null, typeof(Console).GetMethod("WriteLine", new[] { typeof(int) }), parai)//Console.WriteLine(i);
                , Expression.PostIncrementAssign(parai)//i++
                , Expression.Continue(continueLabel)//continue
             ),
             Expression.Goto(breakLabel))//else break
      ), breakLabel, continueLabel);
var total = Expression.Block(new[] { parai },
    loopInit,
    loopExp,
    Expression.Call(null, typeof(Console).GetMethod("WriteLine", new[] { typeof(string) }), Expression.Constant("end loop"))
    );
var loopAct = Expression.Lambda<Action>(total).Compile();
loopAct();

类的操作

Expression.New(); // 来调用一个类型的构造函数。

Expression.Bind(MemberInfo, Expression); // 创建一个 MemberAssignment,它表示字段或属性的初始化。
Expression.MemberBind(); // 创建一个表示递归初始化某个字段或属性的成员的 MemberMemberBinding。
Expression.MemberInit(); // 创建一个 MemberInitExpression。

Expression.Field(); // 创建一个表示访问字段的 MemberExpression。
Expression.Property(); // 使用属性访问器方法创建一个表示访问属性的 MemberExpression。
Expression.PropertyOrField(); // 创建一个表示访问属性或字段的 MemberExpression。
Expression.MakeMemberAccess(); // 创建一个表示访问字段或属性的 MemberExpression。

Expression.Call(); // 方法调用,如在 obj.sampleMethod() 表达式中。
Expression.Invoke(); // 调用委托或 lambda 表达式的运算,如 sampleDelegate.Invoke()。

Expression.GetHashCode(); // 默认哈希函数。
Expression.Equals(Object); // 对象是否相等。
Expression.GetType(); // 获取当前实例的 Type。
Expression.MemberwiseClone(); // 对象的浅表副本。
Expression.TypeEqual(Expression, Type); // 创建一个比较运行时类型标识的 TypeBinaryExpression。
Expression.TypeIs(Expression, Type); // 创建一个 TypeBinaryExpression。

Expression.TypeAs(Expression, Type); // 创建一个表示显式引用或装箱转换的 UnaryExpression(如果转换失败,则提供 null)。
Expression.Unbox(Expression, Type); // 创建一个表示显式取消装箱的 UnaryExpression。

数组、集合操作

// 数组访问
Expression.ArrayAccess(Expression, Expression[]); // 创建一个用于访问数组的 IndexExpression。
Expression.ArrayIndex(Expression, Expression); // 将数组索引运算符应用到一维或多维数组中。
Expression.ArrayLength(Expression); // 创建一个UnaryExpression,它表示获取一维数组的长度的表达式。
Expression.MakeIndex(); // 创建一个 IndexExpression,它表示访问对象中的索引属性。

// 数组、集合的创建初始化
Expression.ElementInit(); // 元素初始化时添加元素
Expression.ListBind(); // 创建一个其成员为字段或属性的 MemberListBinding
Expression.ListInit(); // 初始化集合
Expression.NewArrayBounds(Type, Expression[]); // 创建一个表示创建具有指定类型的数组的 NewArrayExpression。
Expression.NewArrayInit(Type, Expression[]); // 创建一个表示创建一维数组并使用元素列表初始化该数组的 NewArrayExpression。

委托操作

Expression.GetActionType(Type[]); // Action 委托类型
Expression.TryGetActionType(); // 

Expression.GetFuncType(Type[]); // Func<TResult> 委托类型
Expression.TryGetFuncType(); // 
Expression.GetDelegateType(Type[]); // Func<TResult> 或 Action 委托类型

异常操作

Expression.MakeTry(); // 创建一个表示具有指定元素的 try 块的 TryExpression。
Expression.Catch(); // 创建一个表示 catch 语句的
Expression.MakeCatchBlock(); // 创建一个表示具有指定元素的 catch 语句的 CatchBlock
Expression.Rethrow(); // 创建一个 UnaryExpression,它表示重新引发异常。
Expression.Throw(Expression); // 创建一个抛出的异常。
Expression.TryCatch(); // 
Expression.TryCatchFinally(); // 
Expression.TryFault(); // 
Expression.TryFinally(); // 

其他操作

Expression.Block(); // 创建一个 BlockExpression,其中包含多个表达式
Expression.ToString(); // 返回 Expression 的的文本化表示形式。
Expression.Convert(); // 表示类型转换运算
Expression.ConvertChecked(); // 类型转换溢出检查
Expression.Dynamic(); // 动态操作
Expression.MakeDynamic(); // 动态操作
Expression.Empty(); // 空表达式
Expression.IsFalse(); // 返回表达式的计算结果是否为 false。
Expression.IsTrue(); // 返回表达式的计算结果是否为 true。
Expression.Lambda(); // 创建一个表示 Lambda 表达式的表达式树。
Expression.Quote(); // 不理解
Expression.Reduce(); // 略
Expression.ReduceAndCheck();
Expression.ReduceExtensions();
Expression.ReferenceEqual();
Expression.ReferenceNotEqual();
Expression.Return(); // 创建一个表示 return 语句的 GotoExpression。
Expression.RuntimeVariables(); // 创建 RuntimeVariablesExpression 的实例。
Expression.SymbolDocument(); // 存储用于发出源文件调试符号信息所必要的信息,尤其是文件名和唯一的语言标识符。
Expression.VisitChildren(ExpressionVisitor); // 略

应用示例

下面我将提供一个C#示例,其中包含了两个封装好的方法:一个用于通过表达式树和反射修改字段值,另一个用于通过表达式树和反射读取字段值。

using System;  
using System.Linq.Expressions;  
using System.Reflection;  

public class Program  
{  
    public static void Main()  
    {  
        MyClass obj = new MyClass { MyField = 10 };  
        Console.WriteLine("Original value: " + obj.MyField); // 输出: Original value: 10  

        // 使用表达式树和反射设置字段值  
        ReflectionHelper.SetFieldValue<MyClass, int>(obj, "MyField", 20);  
        Console.WriteLine("Modified value: " + obj.MyField); // 输出: Modified value: 20  

        // 使用表达式树和反射读取字段值  
        int readValue = ReflectionHelper.GetFieldValue<MyClass, int>(obj, "MyField");  
        Console.WriteLine("Read value: " + readValue); // 输出: Read value: 20  
    }  
}

public class MyClass  
{  
    public int MyField;  
}

public static class ReflectionHelper  
{  
    // 设置字段值的方法  
    public static void SetFieldValue<TTarget, TValue>(TTarget target, string fieldName, TValue value)  
    {  
        // 获取字段信息  
        FieldInfo fieldInfo = typeof(TTarget).GetField(fieldName);  
        if (fieldInfo == null)  
            throw new ArgumentException($"Field '{fieldName}' not found in type '{typeof(TTarget).Name}'.");  

        // 创建参数表达式(目标对象)  
        ParameterExpression targetExp = Expression.Parameter(typeof(TTarget), "target");  
        // 创建常量表达式(要设置的值)  
        ConstantExpression valueExp = Expression.Constant(value);  
        // 创建字段表达式  
        MemberExpression fieldExp = Expression.Field(targetExp, fieldInfo);  
        // 创建赋值表达式  
        AssignmentExpression assignExp = Expression.Assign(fieldExp, valueExp);  
        // 创建Lambda表达式  
        LambdaExpression lambda = Expression.Lambda(assignExp, targetExp);  
        // 编译Lambda表达式为委托  
        Action<TTarget> action = (Action<TTarget>)lambda.Compile();  
        // 执行委托,设置字段值  
        action(target);  
    }  

    // 读取字段值的方法  
    public static TValue GetFieldValue<TTarget, TValue>(TTarget target, string fieldName)  
    {  
        // 获取字段信息  
        FieldInfo fieldInfo = typeof(TTarget).GetField(fieldName);  
        if (fieldInfo == null)  
            throw new ArgumentException($"Field '{fieldName}' not found in type '{typeof(TTarget).Name}'.");  

        // 创建参数表达式(目标对象)  
        ParameterExpression targetExp = Expression.Parameter(typeof(TTarget), "target");  
        // 创建字段表达式  
        MemberExpression fieldExp = Expression.Field(targetExp, fieldInfo);  
        // 创建Lambda表达式  
        LambdaExpression lambda = Expression.Lambda<Func<TTarget, TValue>>(fieldExp, targetExp);  
        // 编译Lambda表达式为委托  
        Func<TTarget, TValue> func = lambda.Compile();  
        // 执行委托,获取字段值  
        return func(target);  
    }  
}

扩展阅读

官方文档:表达式树
C# 表达式树讲解
C# 表达式树 Expression Trees知识总结
C# 表达式树的知识详解
C# Expression详解(高级)
手把手构建 C# 表达式树
c#:深入理解表达式树

此处评论已关闭