C# 高级教程 - 11 表达式树

表达式树(Expression Tree)将 C# 代码表示为抽象语法树(AST)形式的数据结构,让程序能够”理解”和”操作”代码本身。它是 LINQ to SQL/EF Core、ORM 框架、动态代码生成、AOP 框架的核心基础设施。本文将从表达式树的构建、解析、修改到编译运行,进行全方位的深入讲解。

表达式树基础

1
2
3
4
5
6
7
using System.Linq.Expressions;

// 表达式树 vs 委托
Expression<Func<int, int, int>> expr = (a, b) => a + b;

// 编译器将 lambda 编译为表达式树,而非 IL 代码
// 可以用 DebugView 查看树结构(需安装表达式树可视化器)

编译器区分”编译为委托”和”编译为表达式树”的关键在于赋值对象的类型:

1
2
Func<int, int, int> func = (a, b) => a + b;           // 委托(IL)
Expression<Func<int, int, int>> expr = (a, b) => a + b; // 表达式树(AST)

手动构建表达式树

1
2
3
4
5
6
7
8
9
10
11
12
13
// 等价于: (x, y) => x * y + 2
ParameterExpression x = Expression.Parameter(typeof(int), "x");
ParameterExpression y = Expression.Parameter(typeof(int), "y");

BinaryExpression multiply = Expression.Multiply(x, y);
BinaryExpression add = Expression.Add(multiply, Expression.Constant(2));

Expression<Func<int, int, int>> lambda =
Expression.Lambda<Func<int, int, int>>(add, x, y);

// 编译执行
Func<int, int, int> compiled = lambda.Compile();
int result = compiled(3, 4); // 3*4+2 = 14

常用表达式节点

类型 用途
ParameterExpression 参数声明
ConstantExpression 常量值
BinaryExpression 二元运算
UnaryExpression 一元运算
MethodCallExpression 方法调用
MemberExpression 属性/字段访问
NewExpression 构造函数调用
ConditionalExpression 三元运算符
InvocationExpression 委托调用

解析表达式树

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
static string ExpressionToString(Expression expr)
{
return expr switch
{
ConstantExpression c => c.Value?.ToString() ?? "null",
ParameterExpression p => p.Name ?? "param",
BinaryExpression b => $"({ExpressionToString(b.Left)} {GetBinaryOp(b.NodeType)} {ExpressionToString(b.Right)})",
MethodCallExpression m => $"{m.Method.Name}({string.Join(", ", m.Arguments.Select(ExpressionToString))})",
MemberExpression me => me.Member.Name,
UnaryExpression u => $"({GetUnaryOp(u.NodeType)}{ExpressionToString(u.Operand)})",
_ => expr.ToString()
};
}

static string GetBinaryOp(ExpressionType type) => type switch
{
ExpressionType.Add => "+",
ExpressionType.Subtract => "-",
ExpressionType.Multiply => "*",
ExpressionType.Divide => "/",
ExpressionType.AndAlso => "&&",
ExpressionType.OrElse => "||",
ExpressionType.Equal => "==",
ExpressionType.NotEqual => "!=",
ExpressionType.GreaterThan => ">",
ExpressionType.LessThan => "<",
_ => "?"
};

// 使用
Expression<Func<int, int, int>> addExpr = (a, b) => a * 2 + b;
Console.WriteLine(ExpressionToString(addExpr.Body));
// 输出: ((a * 2) + b)

表达式树访问器(ExpressionVisitor)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 修改表达式树 —— 将所有常量 +1
public class PlusOneVisitor : ExpressionVisitor
{
protected override Expression VisitConstant(ConstantExpression node)
{
if (node.Type == typeof(int) && node.Value is int value)
return Expression.Constant(value + 1);
return base.VisitConstant(node);
}
}

// 使用
Expression<Func<int, int>> original = x => x + 10;
var visitor = new PlusOneVisitor();
var modified = (Expression<Func<int, int>>)visitor.Visit(original);

Console.WriteLine(original.Compile()(5)); // 15
Console.WriteLine(modified.Compile()(5)); // 16(10 被改为 11)

实战:移除表达式中的某些转换

1
2
3
4
5
6
7
8
9
10
// 移除 String.ToUpper 调用(用于大小写不敏感查询优化)
public class RemoveToUpperVisitor : ExpressionVisitor
{
protected override Expression VisitMethodCall(MethodCallExpression node)
{
if (node.Method.Name == nameof(string.ToUpper) && node.Object != null)
return Visit(node.Object); // 直接返回参数,去掉 ToUpper 调用
return base.VisitMethodCall(node);
}
}

实战:动态查询构建器

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
public static class QueryBuilder
{
public static Expression<Func<T, bool>> BuildPredicate<T>(
string propertyName, object value, Operator op)
{
var param = Expression.Parameter(typeof(T), "x");
var property = Expression.Property(param, propertyName);
var constant = Expression.Constant(value);

Expression body = op switch
{
Operator.Equals => Expression.Equal(property, constant),
Operator.GreaterThan => Expression.GreaterThan(property, constant),
Operator.LessThan => Expression.LessThan(property, constant),
Operator.Contains => Expression.Call(
property, "Contains", null, constant),
_ => throw new NotSupportedException()
};

return Expression.Lambda<Func<T, bool>>(body, param);
}

// 组合多个条件
public static Expression<Func<T, bool>> AndAlso<T>(
this Expression<Func<T, bool>> left,
Expression<Func<T, bool>> right)
{
var param = Expression.Parameter(typeof(T), "x");
var body = Expression.AndAlso(
Expression.Invoke(left, param),
Expression.Invoke(right, param));
return Expression.Lambda<Func<T, bool>>(body, param);
}

public static Expression<Func<T, bool>> OrElse<T>(
this Expression<Func<T, bool>> left,
Expression<Func<T, bool>> right)
{
var param = Expression.Parameter(typeof(T), "x");
var body = Expression.OrElse(
Expression.Invoke(left, param),
Expression.Invoke(right, param));
return Expression.Lambda<Func<T, bool>>(body, param);
}
}

public enum Operator { Equals, GreaterThan, LessThan, Contains }

// 使用
var pred1 = QueryBuilder.BuildPredicate<Product>("Price", 100, Operator.GreaterThan);
var pred2 = QueryBuilder.BuildPredicate<Product>("Category", "电子", Operator.Equals);
var combined = pred1.AndAlso(pred2);

var result = products.Where(combined.Compile()).ToList();

表达式树与 ORM

EF Core 的核心就是表达式树翻译器:

1
2
3
4
5
6
7
8
9
10
11
// EF Core 执行的查询
IQueryable<Order> query = dbContext.Orders
.Where(o => o.Amount > 100)
.OrderBy(o => o.Date);

// 内部流程:
// 1. C# 编译 Where/OrderBy 为 MethodCallExpression 链
// 2. IQueryProvider(EF Core)遍历表达式树
// 3. 翻译为 SQL: SELECT * FROM Orders WHERE Amount > 100 ORDER BY Date
// 4. 发送到数据库执行
// 5. 返回结果集映射为对象

动态生成代码 —— 高性能替代反射

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
// 用表达式树生成属性 Getter/Setter(比反射快 10-100 倍)
public static class PropertyAccessor<T>
{
private static readonly Dictionary<string, Func<T, object?>> _getters = new();
private static readonly Dictionary<string, Action<T, object?>> _setters = new();

public static Func<T, object?> GetGetter(string propertyName)
{
if (_getters.TryGetValue(propertyName, out var getter))
return getter;

var param = Expression.Parameter(typeof(T), "obj");
var property = Expression.Property(param, propertyName);
var convert = Expression.Convert(property, typeof(object));
var lambda = Expression.Lambda<Func<T, object?>>(convert, param);
return _getters[propertyName] = lambda.Compile();
}

public static Action<T, object?> GetSetter(string propertyName)
{
if (_setters.TryGetValue(propertyName, out var setter))
return setter;

var param = Expression.Parameter(typeof(T), "obj");
var value = Expression.Parameter(typeof(object), "value");
var property = Expression.Property(param, propertyName);
var convert = Expression.Convert(value, property.Type);
var assign = Expression.Assign(property, convert);
var lambda = Expression.Lambda<Action<T, object?>>(assign, param, value);
return _setters[propertyName] = lambda.Compile();
}
}

// 使用
var product = new Product { Name = "电脑", Price = 5000 };
var getName = PropertyAccessor<Product>.GetGetter("Name");
Console.WriteLine(getName(product)); // "电脑"

var setName = PropertyAccessor<Product>.GetSetter("Price");
setName(product, 5500);
Console.WriteLine(product.Price); // 5500

表达式树限制

1
2
3
4
5
6
7
8
9
10
// 1. 不支持语句体 lambda(花括号)—— 只支持单表达式
Expression<Func<int, int>> invalid = x =>
{
var y = x * 2; // 编译错误!
return y;
};

// 2. 不支持 out/ref 参数
// 3. 不支持 async/await 表达式
// 4. .NET 6+ 新增了少量语句支持

性能数据

1
2
3
4
5
// 调用属性 100 万次对比
// 直接调用: ~3ms
// 表达式树 Getter: ~5ms(首次 ~50ms 编译)
// 反射 GetValue: ~80ms
// DynamicInvoke: ~300ms

总结:表达式树是 C# 元编程能力的顶级体现。理解表达式树对于使用 EF Core 等 ORM 框架至关重要——它解释了为什么某些代码可以翻译为 SQL 而某些不能,以及如何编写可翻译的查询。在需要动态代码生成的场景中,表达式树编译的性能远超反射,是构建高性能框架的首选方案。

ByteFisher
分享编程技术 · 记录钓鱼乐趣
扫码关注
▸ 扫码关注 ◂
分享: