了解 C# 语言中的表达式树

科技   2024-09-30 17:06   上海  

在 C# 中,表达式树是一项强大的功能,它允许开发人员以树状数据结构表示代码。与立即编译和执行的常规 C# 代码不同,表达式树将代码分解为一系列表达式,这些表达式可以在运行时进行检查、修改甚至动态执行。在需要操作或分析代码的方案中,例如生成动态查询、实现域特定语言 (DSL) 或创建 LINQ 提供程序,它们特别有用。

在本文中,我们将探讨什么是表达式树、它们如何工作以及如何在 C# 应用程序中使用它们。

什么是表达式树?

表达式树将代码表示为分层树结构,其中每个节点都是一个表达式。这些表达式可以包括变量、常量、方法调用、运算符等。此树结构反映了 C# 代码的结构,但不直接执行。相反,您可以动态遍历、修改和编译为可执行代码。

表达式树是 .NET 中命名空间的一部分,最初引入表达式树是为了支持语言集成查询 (LINQ)。具体来说,它们允许 LINQ 提供程序(如 Entity Framework)将 LINQ 查询转换为在数据库上运行的 SQL 查询。System.Linq.Expressions

表达式树的关键组件

C# 中的表达式树是使用命名空间中的几个关键组件构建的。一些最常用的类包括:System.Linq.Expressions

  • Expression:所有表达式的基类。

  • LambdaExpression:表示 lambda 表达式。

  • ParameterExpression:表示命名参数(如 lambda 表达式中的变量)。

  • BinaryExpression:表示二进制运算(例如 , )。+-&&

  • ConstantExpression:表示表达式树中的常量。

  • MethodCallExpression:表示表达式树中的方法调用。

这些类允许您构建表示几乎任何类型计算的树。

表达式树示例

让我们从一个简单的示例开始:表示数学运算的 lambda 表达式:

Expression<Func<int, int, int>> expression = (a, b) => a + b;

此 lambda 表达式添加两个整数,并且 .重要的是,此代码不会立即执行,它会生成表达式树。以下是检查它的方法:ab

Console.WriteLine(expression); // Output: (a, b) => (a + b)

为了对此进行细分,表达式树具有:

  1. 两个参数 ( 和 ) 由 表示。abParameterExpression

  2. 由 表示的二进制运算 ()。+BinaryExpression

让我们更进一步,以编程方式检查此表达式树的结构:

var body = (BinaryExpression)expression.Body;  
var left = (ParameterExpression)body.Left;
var right = (ParameterExpression)body.Right;

Console.WriteLine($"Operation: {body.NodeType}"); // Output: Operation: Add
Console.WriteLine($"Left Operand: {left.Name}"); // Output: Left Operand: a
Console.WriteLine($"Right Operand: {right.Name}"); // Output: Right Operand: b

如您所见,树的结构包含有关二元运算及其操作数的信息。

以编程方式创建表达式树

除了内联编写表达式树(就像我们上面所做的那样),你还可以使用类中的工厂方法动态创建它们。这在需要在运行时动态构建表达式的场景中非常有用。Expression

下面是一个以编程方式构造同一表达式树的示例:(a, b) => a + b

var a = Expression.Parameter(typeof(int), "a");  
var b = Expression.Parameter(typeof(int), "b");
var add = Expression.Add(a, b);
var lambda = Expression.Lambda<Func<int, int, int>>(add, a, b);

Console.WriteLine(lambda); // Output: (a, b) => (a + b)

该方法创建参数表达式,该方法创建二进制操作。然后,该方法将这些组合成一个 lambda 表达式。Expression.ParameterExpression.AddExpression.Lambda

编译和执行表达式树

构建表达式树后,您可以将其编译为可执行代码。该方法生成可调用的委托。Compile

var compiledExpression = lambda.Compile();  
var result = compiledExpression(3, 4);

Console.WriteLine(result); // Output: 7

该方法将表达式树转换为委托,它可以像任何常规函数一样执行。CompileFunc<int, int, int>

表达式树和 LINQ

表达式树是 LINQ to SQL、LINQ to Entities(实体框架)和其他 LINQ 提供程序工作的基础。当您编写如下所示的 LINQ 查询时:

var result = dbContext.Customers  
.Where(c => c.Age > 30)
.ToList();

lambda 表达式将转换为表达式树。然后,LINQ 提供程序(例如,Entity Framework)检查此表达式树,将其转换为 SQL 查询 (),并将其发送到数据库执行。c => c.Age > 30SELECT * FROM Customers WHERE Age > 30

修改表达式树

表达式树最强大的功能之一是,您可以在编译或执行它们之前对其进行修改。例如,您可以创建访客模式来遍历和重写表达式树中的节点。

假设我们想把所有出现的加法运算 () 都改为 multiplication ()。我们可以通过子类化 :+*ExpressionVisitor

public class AddToMultiplyVisitor : ExpressionVisitor  
{
protected override Expression VisitBinary(BinaryExpression node)
{
if (node.NodeType == ExpressionType.Add)
{
return Expression.MakeBinary(ExpressionType.Multiply, node.Left, node.Right);
}
return base.VisitBinary(node);
}
}

// Apply the visitor to the expression tree
var visitor = new AddToMultiplyVisitor();
var modifiedExpression = visitor.Visit(lambda);
Console.WriteLine(modifiedExpression); // Output: (a, b) => (a \* b)

在此示例中,我们使用 the 遍历树并将所有加法运算替换为乘法。访问后,修改后的树现在表示乘法运算,而不是加法运算。ExpressionVisitor

表达式树的实际用例

  1. 动态查询生成:表达式树允许您在运行时动态构建 LINQ 查询。这在需要根据用户输入或其他动态条件筛选数据的应用程序非常有用。

  2. 自定义 LINQ 提供程序:通过实现自定义 LINQ 提供程序,您可以以某种方式解释表达式树,将其转换为不同的查询语言(如 SQL、NoSQL 甚至 REST API)。

  3. 代码分析和重构:表达式树可用于分析和操作代码结构,使其成为构建代码分析器、DSL 或 Roslyn 等工具进行重构的理想选择。

  4. 动态代码执行:您可以使用表达式树在运行时创建和执行 C# 代码。当您的应用程序需要根据运行时条件生成和执行自定义 logic 时,这非常有用。

C# 中的表达式树提供了一种独特而强大的方法来表示、检查和操作树状结构中的代码。它们允许开发人员动态地使用代码,为动态查询生成、自定义 LINQ 提供程序和运行时代码执行等高级用例提供了可能性。

通过了解表达式树的工作原理并学习如何创建和修改它们,您可以在 C# 应用程序中释放全新的灵活性。无论您是构建复杂的 LINQ 查询、动态生成代码还是编写自定义提供程序,表达式树都是您武器库中的关键工具。

如果你喜欢我的文章,请给我一个赞!谢谢

架构师老卢
资深软件架构师, 分享编程、软件设计经验, 教授前沿技术, 分享技术资源(每天发布电子书),每天进步一点点...
 最新文章