C# 表达式树 Expression 基础讲解:原理、创建与使用
本文详解 C# 表达式树 (Expression) 的原理、结构及其在 LINQ 和 Entity Framework Core 中的应用。重点解析 Expression 与 Func 的性能差异,避免内存过滤陷阱,并提供通过代码手动构建表达式树的完整步骤与示例。
什么是表达式树
表达式树(Expression Tree)以树形数据结构表示代码逻辑,其中每一个节点代表一种表达式,例如二元运算、方法调用等。通过表达式树,开发者可以动态修改可执行代码、生成动态 SQL 查询语句,或者在 .NET 与动态语言之间建立互操作性。
在 C# 中,表达式树的核心在于它将代码逻辑视为“数据”。我们可以先创建一个简单的 Lambda 表达式:
[HttpGet]
public IActionResult Expression()
{
// 定义一个查询条件:查找年龄大于 18 的用户
Expression<Func<User, bool>> expression = x => x.Age > 18;
return Ok();
}
对于上述的 x.Age > 18 表达式,系统内部将其转换为如下的树状结构:
如果在 Visual Studio 中使用“监视窗口”查看该变量的属性,可以观察到:根节点(NodeType)是 GreaterThan;左子节点(Left)代表访问属性操作(即 x.Age);右子节点(Right)代表常量值 18。基于这些节点信息,我们可以清晰地还原出表达式树的层级关系。
这种树状结构使得底层框架能够解析代码语义,进而将表达式编译为具体的 SQL 查询语句或执行引擎能识别的逻辑。
Expression 与 Func 的本质区别
虽然两者都可用于定义委托,但在处理数据库查询时存在显著差异:
- Expression (表达式树):存储的是运算逻辑的抽象语法树(AST)。它保留了完整的数学和逻辑结构,支持在运行时被遍历、分析和转换。
- Func (普通委托):仅存储一段可在当前环境执行的机器码或 IL 指令。它不保存语法树结构,无法被外部工具解析。
因此,在使用 Entity Framework Core 进行数据库查询时,必须选择 Expression 而非 Func:
// 【正确写法】使用 Expression // EF Core 会解析树结构,将其转换为 SQL:SELECT * FROM User WHERE Age > 18 Expression<Func<User, bool>> expression1 = x => x.Age > 18; var result1 = dbContext.Users.Where(expression1).ToList(); // 【错误写法】使用 Func // EF Core 无法将其转换为 SQL,会执行 SELECT * FROM User,将所有数据拉取到内存后,再由 Linq to Objects 过滤 Func<User, bool> func1 = x => x.Age > 18; var result2 = dbContext.Users.Where(func1).ToList();
简而言之,使用 Func 会导致全表扫描并在内存中进行过滤,这会严重降低性能并增加网络开销。而使用 Expression 可以将过滤逻辑推送到数据库层执行。
手动通过代码创建表达式树
除了使用 Lambda 表达式语法,我们还可以利用 System.Linq.Expressions 命名空间下的工厂方法来逐行构建表达式树。常见的类包括:
ParameterExpression:参数变量(如 lambda 中的 x)。ConstantExpression:常量值(如 18)。MemberExpression:成员访问(如 x.Age)。BinaryExpression:二元运算(如 > 比较)。LambdaExpression:组合整个表达式的根节点。
由于这些类的构造函数通常是内部公开的,我们不能直接 new 它们,必须调用 Expression.Parameter, Expression.Constant, Expression.MakeMemberAccess, Expression.GreaterThanOrEqual 以及 Expression.Lambda 等静态方法。
第1步:创建参数节点
首先创建表达式的输入参数,指定类型和名称。
// 对应 Lambda 表达式中的 'u' 变量 ParameterExpression parameterExpression = Expression.Parameter(typeof(User), "u");
第2步:构建叶子节点(左、右子节点)
按照运算符的结合方向,先构建子表达式:
- 右节点:常量 18。
- 左节点:成员访问操作,获取 User 的 Age 属性。
// 右节点:值为 18 的常量
ConstantExpression right = Expression.Constant(18);
// 左节点:User.属性(Age)
MemberExpression left = Expression.MakeMemberAccess(parameterExpression, typeof(User).GetProperty("Age"));
第3步:组合成主体节点 (Body)
使用二元表达式类将左右节点连接起来,表示 “>=” 比较:
// 左 >= 右 BinaryExpression body = Expression.GreaterThanOrEqual(left, right);
第4步:组装最终 Lambda 表达式
将主体和参数放入 Lambda 节点中,完成整个表达式的构建:
Expression<Func<User, bool>> expression = Expression.Lambda<Func<User, bool>>(body, parameterExpression);
以上代码片段展示了如何在不写 Lambda 符号的情况下,从底层 API 一步步拼装出完全相同的表达式树结构。这在需要高度动态生成复杂查询条件的场景中非常有用。
以上就是C#表达式树 Expression 基础讲解的详细内容,更多关于C#高级特性的资料请关注本站其它相关文章!
本站声明
本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。













