添加链接
link管理
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接

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

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

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

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

表达式树 可以用于诸如 解析XML JSON数据 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]{
// 使用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#:深入理解表达式树