C#的表达式树(System.Linq.Expressions)
概述
在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]{
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#:深入理解表达式树
最后更新于 2024-04-11 09:19:11 并被添加「」标签,已有 1751 位童鞋阅读过。
本站使用「署名 4.0 国际」创作共享协议,可自由转载、引用,但需署名作者且注明文章出处
此处评论已关闭