表达式树(Expression Trees)

  • ~19.44K 字
  1. 1. 什么是表达式树?
    1. 1.1. 通俗理解
    2. 1.2. 一个简单的例子
    3. 1.3. 为什么需要表达式树?
  2. 2. Expression 和 Func 的区别
    1. 2.1. Func<T> - 委托
    2. 2.2. Expression<Func<T>> - 表达式树
    3. 2.3. 对比表格
    4. 2.4. 实际应用场景对比
    5. 2.5. 总结
  3. 3. 查看表达式树的结构
    1. 3.1. Visual Studio通过自动窗口查看
    2. 3.2. 代码查看表达式树
    3. 3.3. 递归遍历表达式树
  4. 4. 总结
    1. 4.1. 关键要点
    2. 4.2. 推荐阅读

在 .NET 开发中,表达式树是一个强大但容易被忽视的特性。如果你使用过 Entity Framework 或 LINQ to SQL,其实你已经在间接使用表达式树了。


什么是表达式树?

表达式树(Expression Tree) 是用树形数据结构来表示代码逻辑运算的技术,它让我们可以在运行时访问逻辑运算结构。表达式树在.net中对应Expression<TDelegate>类型。

通俗理解

  • 普通委托:是一段可执行的代码
  • 表达式树:是描述代码结构的数据,可以被分析、修改、翻译成其他形式(如 SQL)

一个简单的例子

1
2
3
4
5
6
7
// 普通委托:直接执行
Func<int, bool> func = x => x > 5;
bool result = func(10); // 直接执行,返回 true

// 表达式树:代码的数据表示
Expression<Func<int, bool>> expr = x => x > 5;
// expr 不是可执行代码,而是一个树形结构,描述了"x > 5"这个表达式

为什么需要表达式树?

表达式树的主要用途是将 C# 代码翻译成其他形式

  1. LINQ to SQL / Entity Framework:将 C# 查询翻译成 SQL 语句
  2. 动态查询构建:根据用户输入动态生成查询条件
  3. 代码分析:检查代码结构、提取元数据
  4. 序列化/反序列化:将代码逻辑保存或传输

Expression 和 Func 的区别

Func<T> - 委托

Func<T> 是一个委托类型,代表可执行的代码

1
2
Func<int, int, int> add = (a, b) => a + b;
int result = add(3, 5); // 执行代码,返回 8

特点

  • 编译后直接生成 IL 代码
  • 可以直接调用执行
  • 无法查看内部结构

Expression<Func<T>> - 表达式树

Expression<Func<T>> 是一个表达式树类型,代表代码的数据结构

1
2
Expression<Func<int, int, int>> addExpr = (a, b) => a + b;
// addExpr 是一个树形结构,不能直接执行

特点

  • 编译后生成表达式树对象(包含 Body、Parameters 等属性)
  • 不能直接调用,需要先编译成委托
  • 可以分析、修改、翻译

对比表格

维度 Func Expression<Func>
本质 委托(可执行代码) 表达式树(代码的数据结构)
是否可执行 ✅ 直接执行 ❌ 需要先编译成委托
是否可分析 ❌ 无法查看内部结构 ✅ 可以访问树形结构
典型用途 LINQ to Objects、回调函数 LINQ to SQL、动态查询

实际应用场景对比

1
2
3
4
5
6
7
// LINQ to Objects:使用 Func(在内存中执行)
List<int> numbers = new List<int> { 1, 2, 3, 4, 5 };
var result1 = numbers.Where(x => x > 3); // 直接在内存中过滤

// LINQ to Entities:使用 Expression(翻译成 SQL)
DbSet<User> users = dbContext.Users;
var result2 = users.Where(x => x.Age > 18); // 翻译成 SQL: SELECT * FROM Users WHERE Age > 18

总结

Expression类似于源代码,Func类似编译后的二进制程序。


查看表达式树的结构

表达式树是一个树形结构,可以通过其属性查看内部组成。

Visual Studio通过自动窗口查看

1
Expression<Func<int, bool>> expr = x => x > 5;

可以看到expr NodeType是Lambda,Body下面有Left {x}和Right {5} ,Body的 NodeType 是 GreaterThan,它是 二元运算符节点类型 BinaryExpression ,GreaterThan表示大于

代码查看表达式树

使用包ExpressionTreeToString

1
2
3
4
5
Expression<Func<int,bool>> expr = x => x > 5;
Console.WriteLine(expr);
Console.WriteLine(expr.Body);

Console.WriteLine(expr.ToString("Object notation","C#"));

可以看到,表达式树不同类型的节点对应不同类型,这些类型直接间接继承Expression。如上面Body就是二元运算符节点类型 BinaryExpression、Lambda 表达式 LambdaExpression(如 x => x > 10)、方法调用 MethodCallExpression(如 obj.ToString())、成员访问 MemberExpression (如 obj.Name、x.Age)、常量 ConstantExpression 、参数 ParameterExpression。

而NodeType表示标识当前表达式节点的类型,是ExpressionType枚举值。

ExpressionType
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
#region 程序集 System.Linq.Expressions, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
// C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\10.0.0\ref\net10.0\System.Linq.Expressions.dll
#endregion

namespace System.Linq.Expressions
{
//
// 摘要:
// Describes the node types for the nodes of an expression tree.
public enum ExpressionType
{
//
// 摘要:
// An addition operation, such as a + b, without overflow checking, for numeric
// operands.
Add = 0,
//
// 摘要:
// An addition operation, such as (a + b), with overflow checking, for numeric operands.
AddChecked = 1,
//
// 摘要:
// A bitwise or logical AND operation, such as (a & b) in C# and (a And b) in Visual
// Basic.
And = 2,
//
// 摘要:
// A conditional AND operation that evaluates the second operand only if the first
// operand evaluates to true. It corresponds to (a && b) in C# and (a AndAlso b)
// in Visual Basic.
AndAlso = 3,
//
// 摘要:
// An operation that obtains the length of a one-dimensional array, such as array.Length.
ArrayLength = 4,
//
// 摘要:
// An indexing operation in a one-dimensional array, such as array[index] in C#
// or array(index) in Visual Basic.
ArrayIndex = 5,
//
// 摘要:
// A method call, such as in the obj.sampleMethod() expression.
Call = 6,
//
// 摘要:
// A node that represents a null coalescing operation, such as (a ?? b) in C# or
// If(a, b) in Visual Basic.
Coalesce = 7,
//
// 摘要:
// A conditional operation, such as a > b ? a : b in C# or If(a > b, a, b) in Visual
// Basic.
Conditional = 8,
//
// 摘要:
// A constant value.
Constant = 9,
//
// 摘要:
// A cast or conversion operation, such as (SampleType)obj in C#or CType(obj, SampleType)
// in Visual Basic. For a numeric conversion, if the converted value is too large
// for the destination type, no exception is thrown.
Convert = 10,
//
// 摘要:
// A cast or conversion operation, such as (SampleType)obj in C#or CType(obj, SampleType)
// in Visual Basic. For a numeric conversion, if the converted value does not fit
// the destination type, an exception is thrown.
ConvertChecked = 11,
//
// 摘要:
// A division operation, such as (a / b), for numeric operands.
Divide = 12,
//
// 摘要:
// A node that represents an equality comparison, such as (a == b) in C# or (a =
// b) in Visual Basic.
Equal = 13,
//
// 摘要:
// A bitwise or logical XOR operation, such as (a ^ b) in C# or (a Xor b) in Visual
// Basic.
ExclusiveOr = 14,
//
// 摘要:
// A "greater than" comparison, such as (a > b).
GreaterThan = 15,
//
// 摘要:
// A "greater than or equal to" comparison, such as (a >= b).
GreaterThanOrEqual = 16,
//
// 摘要:
// An operation that invokes a delegate or lambda expression, such as sampleDelegate.Invoke().
Invoke = 17,
//
// 摘要:
// A lambda expression, such as a => a + a in C# or Function(a) a + a in Visual
// Basic.
Lambda = 18,
//
// 摘要:
// A bitwise left-shift operation, such as (a << b).
LeftShift = 19,
//
// 摘要:
// A "less than" comparison, such as (a < b).
LessThan = 20,
//
// 摘要:
// A "less than or equal to" comparison, such as (a <= b).
LessThanOrEqual = 21,
//
// 摘要:
// An operation that creates a new System.Collections.IEnumerable object and initializes
// it from a list of elements, such as new List<SampleType>(){ a, b, c } in C# or
// Dim sampleList = { a, b, c } in Visual Basic.
ListInit = 22,
//
// 摘要:
// An operation that reads from a field or property, such as obj.SampleProperty.
MemberAccess = 23,
//
// 摘要:
// An operation that creates a new object and initializes one or more of its members,
// such as new Point { X = 1, Y = 2 } in C# or New Point With {.X = 1, .Y = 2} in
// Visual Basic.
MemberInit = 24,
//
// 摘要:
// An arithmetic remainder operation, such as (a % b) in C# or (a Mod b) in Visual
// Basic.
Modulo = 25,
//
// 摘要:
// A multiplication operation, such as (a * b), without overflow checking, for numeric
// operands.
Multiply = 26,
//
// 摘要:
// An multiplication operation, such as (a * b), that has overflow checking, for
// numeric operands.
MultiplyChecked = 27,
//
// 摘要:
// An arithmetic negation operation, such as (-a). The object a should not be modified
// in place.
Negate = 28,
//
// 摘要:
// A unary plus operation, such as (+a). The result of a predefined unary plus operation
// is the value of the operand, but user-defined implementations might have unusual
// results.
UnaryPlus = 29,
//
// 摘要:
// An arithmetic negation operation, such as (-a), that has overflow checking. The
// object a should not be modified in place.
NegateChecked = 30,
//
// 摘要:
// An operation that calls a constructor to create a new object, such as new SampleType().
New = 31,
//
// 摘要:
// An operation that creates a new one-dimensional array and initializes it from
// a list of elements, such as new SampleType[]{a, b, c} in C# or New SampleType(){a,
// b, c} in Visual Basic.
NewArrayInit = 32,
//
// 摘要:
// An operation that creates a new array, in which the bounds for each dimension
// are specified, such as new SampleType[dim1, dim2] in C# or New SampleType(dim1,
// dim2) in Visual Basic.
NewArrayBounds = 33,
//
// 摘要:
// A bitwise complement or logical negation operation. In C#, it is equivalent to
// (~a) for integral types and to (!a) for Boolean values. In Visual Basic, it is
// equivalent to (Not a). The object a should not be modified in place.
Not = 34,
//
// 摘要:
// An inequality comparison, such as (a != b) in C# or (a <> b) in Visual Basic.
NotEqual = 35,
//
// 摘要:
// A bitwise or logical OR operation, such as (a | b) in C# or (a Or b) in Visual
// Basic.
Or = 36,
//
// 摘要:
// A short-circuiting conditional OR operation, such as (a || b) in C# or (a OrElse
// b) in Visual Basic.
OrElse = 37,
//
// 摘要:
// A reference to a parameter or variable that is defined in the context of the
// expression. For more information, see System.Linq.Expressions.ParameterExpression.
Parameter = 38,
//
// 摘要:
// A mathematical operation that raises a number to a power, such as (a ^ b) in
// Visual Basic.
Power = 39,
//
// 摘要:
// An expression that has a constant value of type System.Linq.Expressions.Expression.
// A System.Linq.Expressions.ExpressionType.Quote node can contain references to
// parameters that are defined in the context of the expression it represents.
Quote = 40,
//
// 摘要:
// A bitwise right-shift operation, such as (a >> b).
RightShift = 41,
//
// 摘要:
// A subtraction operation, such as (a - b), without overflow checking, for numeric
// operands.
Subtract = 42,
//
// 摘要:
// An arithmetic subtraction operation, such as (a - b), that has overflow checking,
// for numeric operands.
SubtractChecked = 43,
//
// 摘要:
// An explicit reference or boxing conversion in which null is supplied if the conversion
// fails, such as (obj as SampleType) in C# or TryCast(obj, SampleType) in Visual
// Basic.
TypeAs = 44,
//
// 摘要:
// A type test, such as obj is SampleType in C# or TypeOf obj is SampleType in Visual
// Basic.
TypeIs = 45,
//
// 摘要:
// An assignment operation, such as (a = b).
Assign = 46,
//
// 摘要:
// A block of expressions.
Block = 47,
//
// 摘要:
// Debugging information.
DebugInfo = 48,
//
// 摘要:
// A unary decrement operation, such as (a - 1) in C# and Visual Basic. The object
// a should not be modified in place.
Decrement = 49,
//
// 摘要:
// A dynamic operation.
Dynamic = 50,
//
// 摘要:
// A default value.
Default = 51,
//
// 摘要:
// An extension expression.
Extension = 52,
//
// 摘要:
// A "go to" expression, such as goto Label in C# or GoTo Label in Visual Basic.
Goto = 53,
//
// 摘要:
// A unary increment operation, such as (a + 1) in C# and Visual Basic. The object
// a should not be modified in place.
Increment = 54,
//
// 摘要:
// An index operation or an operation that accesses a property that takes arguments.
Index = 55,
//
// 摘要:
// A label.
Label = 56,
//
// 摘要:
// A list of run-time variables. For more information, see System.Linq.Expressions.RuntimeVariablesExpression.
RuntimeVariables = 57,
//
// 摘要:
// A loop, such as for or while.
Loop = 58,
//
// 摘要:
// A switch operation, such as switch in C# or Select Case in Visual Basic.
Switch = 59,
//
// 摘要:
// An operation that throws an exception, such as throw new Exception().
Throw = 60,
//
// 摘要:
// A try-catch expression.
Try = 61,
//
// 摘要:
// An unbox value type operation, such as unbox and unbox.any instructions in MSIL.
Unbox = 62,
//
// 摘要:
// An addition compound assignment operation, such as (a += b), without overflow
// checking, for numeric operands.
AddAssign = 63,
//
// 摘要:
// A bitwise or logical AND compound assignment operation, such as (a &= b) in C#.
AndAssign = 64,
//
// 摘要:
// An division compound assignment operation, such as (a /= b), for numeric operands.
DivideAssign = 65,
//
// 摘要:
// A bitwise or logical XOR compound assignment operation, such as (a ^= b) in C#.
ExclusiveOrAssign = 66,
//
// 摘要:
// A bitwise left-shift compound assignment, such as (a <<= b).
LeftShiftAssign = 67,
//
// 摘要:
// An arithmetic remainder compound assignment operation, such as (a %= b) in C#.
ModuloAssign = 68,
//
// 摘要:
// A multiplication compound assignment operation, such as (a *= b), without overflow
// checking, for numeric operands.
MultiplyAssign = 69,
//
// 摘要:
// A bitwise or logical OR compound assignment, such as (a |= b) in C#.
OrAssign = 70,
//
// 摘要:
// A compound assignment operation that raises a number to a power, such as (a ^=
// b) in Visual Basic.
PowerAssign = 71,
//
// 摘要:
// A bitwise right-shift compound assignment operation, such as (a >>= b).
RightShiftAssign = 72,
//
// 摘要:
// A subtraction compound assignment operation, such as (a -= b), without overflow
// checking, for numeric operands.
SubtractAssign = 73,
//
// 摘要:
// An addition compound assignment operation, such as (a += b), with overflow checking,
// for numeric operands.
AddAssignChecked = 74,
//
// 摘要:
// A multiplication compound assignment operation, such as (a *= b), that has overflow
// checking, for numeric operands.
MultiplyAssignChecked = 75,
//
// 摘要:
// A subtraction compound assignment operation, such as (a -= b), that has overflow
// checking, for numeric operands.
SubtractAssignChecked = 76,
//
// 摘要:
// A unary prefix increment, such as (++a). The object a should be modified in place.
PreIncrementAssign = 77,
//
// 摘要:
// A unary prefix decrement, such as (--a). The object a should be modified in place.
PreDecrementAssign = 78,
//
// 摘要:
// A unary postfix increment, such as (a++). The object a should be modified in
// place.
PostIncrementAssign = 79,
//
// 摘要:
// A unary postfix decrement, such as (a--). The object a should be modified in
// place.
PostDecrementAssign = 80,
//
// 摘要:
// An exact type test.
TypeEqual = 81,
//
// 摘要:
// A ones complement operation, such as (~a) in C#.
OnesComplement = 82,
//
// 摘要:
// A true condition value.
IsTrue = 83,
//
// 摘要:
// A false condition value.
IsFalse = 84
}
}

递归遍历表达式树

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
using System.Linq.Expressions;

namespace ExpressionTreeDemo
{
internal class Program
{
static void Main(string[] args)
{
// 构建示例表达式树:p => p.Age > 18 && p.Salary >= 10000m
Expression<Func<Person, bool>> expr = p => p.Age > 18 && p.Salary >= 10000m;

Console.WriteLine("=== 递归遍历表达式树 ===");
TraverseExpression(expr, depth: 0); // depth 用于缩进,直观显示层级
}

/// <summary>
/// 递归遍历表达式树,打印所有节点信息
/// </summary>
/// <param name="node">当前要遍历的节点</param>
/// <param name="depth">当前节点的层级(用于缩进)</param>
static void TraverseExpression(Expression node, int depth)
{
if (node == null) return;

// 缩进:层级越深,缩进越多(直观显示树形结构)
string indent = new string(' ', depth * 2);

string nodeInfo = GetNodeInfo(node);
Console.WriteLine($"{indent}[{depth}] {node.NodeType}{nodeInfo}");

// 根据节点类型,递归处理子节点
switch (node)
{
// 1. 二元表达式(如 GreaterThan、AndAlso、OrElse)
case BinaryExpression binaryExpr:
TraverseExpression(binaryExpr.Left, depth + 1); // 递归左子节点
TraverseExpression(binaryExpr.Right, depth + 1); // 递归右子节点
break;

// 2. 属性访问表达式(如 p.Age、p.Salary)
case MemberExpression memberExpr:
TraverseExpression(memberExpr.Expression, depth + 1);
break;

// 3. Lambda 表达式
case LambdaExpression lambdaExpr:
foreach (var param in lambdaExpr.Parameters)
{
TraverseExpression(param, depth + 1); // 递归参数
}
TraverseExpression(lambdaExpr.Body, depth + 1); // 递归表达式体
break;

// 4. 方法调用表达式
case MethodCallExpression methodExpr:
TraverseExpression(methodExpr.Object, depth + 1); // 递归调用对象(静态方法为 null)
foreach (var arg in methodExpr.Arguments)
{
TraverseExpression(arg, depth + 1); // 递归所有参数
}
break;

// 5. 常量表达式
case ConstantExpression _:
break;

// 6. 参数表达式
case ParameterExpression _:
break;

// 其他节点类型(如 UnaryExpression、NewExpression )
default:
Console.WriteLine($"{indent}[{depth}] 未处理的节点类型:{node.GetType().Name}");
break;
}
}

/// <summary>
/// 获取节点的关键信息
/// </summary>
static string GetNodeInfo(Expression node)
{
return node switch
{
ParameterExpression param => $"参数:{param.Name}(类型:{param.Type.Name})",
ConstantExpression constant => $"常量:{constant.Value}(类型:{constant.Type.Name})",
MemberExpression member => $"属性:{member.Member.Name}(所属类型:{member.Member.DeclaringType.Name})",
BinaryExpression binary => $"二元运算:{binary.NodeType}(返回类型:{binary.Type.Name})",
LambdaExpression lambda => $"Lambda 表达式(参数数:{lambda.Parameters.Count},返回类型:{lambda.ReturnType.Name})",
MethodCallExpression method => $"方法调用:{method.Method.Name}(参数数:{method.Arguments.Count})",
_ => $"类型:{node.GetType().Name}"
};
}
}
class Person
{
public string Name { get; set; }
public int Age { get; set; }
public decimal Salary { get; set; }
}
}


总结

概念 说明
表达式树 代码的树形数据结构,可以被分析和翻译
Func 可执行的委托,用于 LINQ to Objects
Expression<Func> 表达式树,用于 LINQ to SQL/EF

关键要点

  1. 表达式树是”代码的数据”,不是”可执行代码”
  2. Expression<Func<T>> 可以被翻译成 SQL 或其他语言
  3. 手动构建表达式树适用于动态场景,但不要过度使用
  4. 优先使用 Lambda 表达式,保持代码简洁

推荐阅读