表达式树进阶
大约 13 分钟约 3938 字
表达式树进阶
简介
表达式树(Expression Tree)将代码表示为数据结构,可在运行时分析和转换。理解表达式树的高级用法,有助于构建动态查询、ORM 映射和代码生成工具。表达式树是 LINQ 的基础——当你写 dbContext.Users.Where(u => u.Age > 18) 时,编译器将 lambda 表达式转换为表达式树(Expression<Func<User, bool>>),而不是普通委托。这使得 ORM 框架(如 EF Core)能够解析表达式树的结构,将其翻译为 SQL 查询。表达式树本质上是一个抽象语法树(AST),每个节点代表代码中的一个操作。
特点
核心概念
表达式树的结构
表达式树由不同类型的表达式节点组成,常见节点类型包括:
| 节点类型 | 说明 | 示例 |
|---|---|---|
| ParameterExpression | 参数 | x |
| ConstantExpression | 常量 | 42, "hello" |
| BinaryExpression | 二元运算 | x + y, a > b |
| UnaryExpression | 一元运算 | -x, !flag |
| MemberExpression | 成员访问 | user.Name, obj.Prop |
| MethodCallExpression | 方法调用 | str.Contains("x") |
| LambdaExpression | Lambda | x => x.Age > 18 |
| ConditionalExpression | 条件表达式 | a ? b : c |
| NewExpression | 构造函数调用 | new User() |
表达式树 vs 普通委托
// 普通委托 — 编译为 IL 方法,直接执行
Func<User, bool> funcDelegate = u => u.Age > 18;
bool result = funcDelegate(user); // 直接调用
// 表达式树 — 编译为数据结构,可以分析
Expression<Func<User, bool>> expression = u => u.Age > 18;
// expression.Body 是一个 BinaryExpression
// expression.Parameters[0] 是一个 ParameterExpression关键区别:当你使用 Expression<> 包装 lambda 时,编译器不会将其编译为 IL,而是构建一棵表达式树。这让你可以在运行时检查和修改代码结构。
实现
动态查询构建
using System.Linq.Expressions;
// 动态构建 Where 条件
public static class DynamicQueryBuilder
{
public static Expression<Func<T, bool>> BuildPredicate<T>(
string propertyName, string op, object value)
{
var param = Expression.Parameter(typeof(T), "x");
var property = Expression.Property(param, propertyName);
var constant = Expression.Constant(
Convert.ChangeType(value, property.Type));
BinaryExpression body = op switch
{
"==" => Expression.Equal(property, constant),
"!=" => Expression.NotEqual(property, constant),
">" => Expression.GreaterThan(property, constant),
"<" => Expression.LessThan(property, constant),
">=" => Expression.GreaterThanOrEqual(property, constant),
"<=" => Expression.LessThanOrEqual(property, constant),
"contains" => Expression.Call(
property, "Contains", null, constant),
"startswith" => Expression.Call(
property, "StartsWith", null, constant),
_ => throw new NotSupportedException($"不支持的操作: {op}")
};
return Expression.Lambda<Func<T, bool>>(body, param);
}
// 组合多个条件(使用 ExpressionVisitor 避免 Invoke 问题)
public static Expression<Func<T, bool>> And<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);
}
}
// 使用 — 动态筛选
var filter = DynamicQueryBuilder.BuildPredicate<User>("Age", ">=", 18)
.And(DynamicQueryBuilder.BuildPredicate<User>("IsActive", "==", true));
var users = await dbContext.Users.Where(filter).ToListAsync();ExpressionVisitor 自定义修改
using System.Linq.Expressions;
// 替换表达式中的参数(解决参数不一致问题)
public class ParameterReplacer : ExpressionVisitor
{
private readonly ParameterExpression _oldParam;
private readonly Expression _newExpr;
public ParameterReplacer(ParameterExpression oldParam, Expression newExpr)
{
_oldParam = oldParam;
_newExpr = newExpr;
}
protected override Expression VisitParameter(ParameterExpression node)
=> node == _oldParam ? _newExpr : base.VisitParameter(node);
}
// 表达式合并(无 Invoke — EF Core 兼容)
public static class ExpressionExtensions
{
public static Expression<Func<T, bool>> Combine<T>(
this Expression<Func<T, bool>> first,
Expression<Func<T, bool>> second,
Func<Expression, Expression, BinaryExpression> merge)
{
var param = Expression.Parameter(typeof(T), "x");
var left = new ParameterReplacer(first.Parameters[0], param)
.Visit(first.Body);
var right = new ParameterReplacer(second.Parameters[0], param)
.Visit(second.Body);
return Expression.Lambda<Func<T, bool>>(merge(left!, right!), param);
}
// 便捷方法
public static Expression<Func<T, bool>> AndAlso<T>(
this Expression<Func<T, bool>> first,
Expression<Func<T, bool>> second)
=> Combine(first, second, Expression.AndAlso);
public static Expression<Func<T, bool>> OrElse<T>(
this Expression<Func<T, bool>> first,
Expression<Func<T, bool>> second)
=> Combine(first, second, Expression.OrElse);
}
// 使用
var expr1 = (Expression<Func<User, bool>>)(u => u.Age > 18);
var expr2 = (Expression<Func<User, bool>>)(u => u.IsActive);
var combined = expr1.Combine(expr2, Expression.AndAlso);
// 等价于 u => u.Age > 18 && u.IsActive
// 无 Expression.Invoke,EF Core 可以正确翻译为 SQL属性访问器缓存
using System.Linq.Expressions;
using System.Collections.Concurrent;
public static class PropertyAccessorCache
{
private static readonly ConcurrentDictionary<(Type, string), Func<object, object?>>
_getterCache = new();
private static readonly ConcurrentDictionary<(Type, string), Action<object, object?>>
_setterCache = new();
// 获取属性值(比反射快 10-100 倍)
public static Func<object, object?> GetGetter(Type type, string propertyName)
{
return _getterCache.GetOrAdd((type, propertyName), key =>
{
var param = Expression.Parameter(typeof(object), "obj");
var cast = Expression.Convert(param, key.Item1);
var property = Expression.Property(cast, key.Item2);
var convert = Expression.Convert(property, typeof(object));
var lambda = Expression.Lambda<Func<object, object?>>(convert, param);
return lambda.Compile();
});
}
// 设置属性值
public static Action<object, object?> GetSetter(Type type, string propertyName)
{
return _setterCache.GetOrAdd((type, propertyName), key =>
{
var param = Expression.Parameter(typeof(object), "obj");
var value = Expression.Parameter(typeof(object), "value");
var cast = Expression.Convert(param, key.Item1);
var property = Expression.Property(cast, key.Item2);
var convertedValue = Expression.Convert(value, property.Type);
var assign = Expression.Assign(property, convertedValue);
var lambda = Expression.Lambda<Action<object, object?>>(assign, param, value);
return lambda.Compile();
});
}
}
// 使用 — 反射的性能替代
var getter = PropertyAccessorCache.GetGetter(typeof(User), "Name");
var name = getter(user); // 接近直接访问属性的性能
var setter = PropertyAccessorCache.GetSetter(typeof(User), "Name");
setter(user, "新名字");表达式树解析与调试
using System.Linq.Expressions;
// 打印表达式树的结构
public class ExpressionPrinter : ExpressionVisitor
{
private int _indent;
public void Print(Expression expression)
{
_indent = 0;
Visit(expression);
}
protected override Expression VisitBinary(BinaryExpression node)
{
WriteLine($"Binary: {node.NodeType} ({node.Left.GetType().Name}, {node.Right.GetType().Name})");
_indent++;
Visit(node.Left);
Visit(node.Right);
_indent--;
return node;
}
protected override Expression VisitParameter(ParameterExpression node)
{
WriteLine($"Parameter: {node.Name} ({node.Type.Name})");
return node;
}
protected override Expression VisitConstant(ConstantExpression node)
{
WriteLine($"Constant: {node.Value} ({node.Type.Name})");
return node;
}
protected override Expression VisitMember(MemberExpression node)
{
WriteLine($"Member: {node.Member.Name} ({node.Type.Name})");
return node;
}
protected override Expression VisitMethodCall(MethodCallExpression node)
{
WriteLine($"MethodCall: {node.Method.Name} ({node.Method.DeclaringType?.Name})");
return node;
}
private void WriteLine(string text)
{
Console.WriteLine(new string(' ', _indent * 2) + text);
}
}
// 使用
Expression<Func<User, bool>> expr = u => u.Age > 18 && u.IsActive;
var printer = new ExpressionPrinter();
printer.Print(expr);
// 输出:
// Binary: AndAlso (ParameterExpression, ParameterExpression)
// Binary: GreaterThan (MemberExpression, ConstantExpression)
// Member: Age (Int32)
// Constant: 18 (Int32)
// Member: IsActive (Boolean)动态对象创建
using System.Linq.Expressions;
// 动态编译对象构造函数
public static class ObjectFactory
{
private static readonly ConcurrentDictionary<Type, Func<object>>
_constructorCache = new();
public static Func<object> CreateConstructor(Type type)
{
return _constructorCache.GetOrAdd(type, t =>
{
var newExpr = Expression.New(t);
var convert = Expression.Convert(newExpr, typeof(object));
return Expression.Lambda<Func<object>>(convert).Compile();
});
}
// 动态创建带参数的对象
public static Func<object[], object> CreateConstructor(Type type, Type[] paramTypes)
{
var paramsExpr = Expression.Parameter(typeof(object[]), "args");
var paramExprs = new Expression[paramTypes.Length];
for (int i = 0; i < paramTypes.Length; i++)
{
var index = Expression.Constant(i);
var arrayAccess = Expression.ArrayIndex(paramsExpr, index);
var cast = Expression.Convert(arrayAccess, paramTypes[i]);
paramExprs[i] = cast;
}
var constructor = type.GetConstructor(paramTypes);
if (constructor == null)
throw new InvalidOperationException($"类型 {type} 没有匹配的构造函数");
var newExpr = Expression.New(constructor, paramExprs);
var convert = Expression.Convert(newExpr, typeof(object));
return Expression.Lambda<Func<object[], object>>(convert, paramsExpr).Compile();
}
}
// 使用
var userFactory = ObjectFactory.CreateConstructor(typeof(User));
var user = (User)userFactory();
var paramFactory = ObjectFactory.CreateConstructor(
typeof(User), new[] { typeof(string), typeof(int) });
var user2 = (User)paramFactory(new object[] { "张三", 25 });表达式树与 EF Core 查询翻译
using Microsoft.EntityFrameworkCore;
// 自定义表达式翻译器 — 将 C# 方法翻译为 SQL 函数
public class CustomQueryTranslator
{
// 示例:将自定义的 ContainsAny 方法翻译为 SQL 的 EXISTS 子查询
public static async Task<List<User>> SearchUsers(
AppDbContext db, string keyword, List<int> roleIds)
{
// 动态构建查询
var query = db.Users.AsQueryable();
// 动态添加条件
if (!string.IsNullOrEmpty(keyword))
{
query = query.Where(u =>
u.Name.Contains(keyword) || u.Email.Contains(keyword));
}
if (roleIds != null && roleIds.Count > 0)
{
query = query.Where(u => roleIds.Contains(u.RoleId));
}
return await query.ToListAsync();
}
}性能考虑
using System.Diagnostics;
using System.Linq.Expressions;
// Compile() 的开销 — 只编译一次,缓存复用
public class CompiledExpressionBenchmark
{
public void Benchmark()
{
Expression<Func<int, int, int>> addExpr = (a, b) => a + b;
// 首次编译较慢(约几十微秒)
var sw = Stopwatch.StartNew();
var compiled = addExpr.Compile();
sw.Stop();
Console.WriteLine($"编译耗时: {sw.ElapsedTicks} ticks");
// 编译后执行接近手写代码
sw.Restart();
for (int i = 0; i < 1_000_000; i++)
compiled(1, 2);
sw.Stop();
Console.WriteLine($"编译后执行 100 万次: {sw.ElapsedMilliseconds} ms");
// 对比直接委托
Func<int, int, int> direct = (a, b) => a + b;
sw.Restart();
for (int i = 0; i < 1_000_000; i++)
direct(1, 2);
sw.Stop();
Console.WriteLine($"直接委托执行 100 万次: {sw.ElapsedMilliseconds} ms");
}
}优点
缺点
总结
表达式树将代码表示为数据结构,支持运行时分析、修改和编译。动态查询构建用于运行时生成 LINQ Where 条件。ExpressionVisitor 遍历和修改表达式树,是构建自定义翻译器的基础。编译后的委托性能接近手写代码,可缓存复用。建议在 ORM 查询翻译、动态规则引擎、反射性能优化等场景使用表达式树。
关键知识点
Expression<Func<T>>是表达式树,Func<T>是编译后的委托Expression.Invoke在某些提供程序(如 EF Core)中不被支持,需要用ExpressionVisitor替换参数Compile()有编译开销,必须缓存编译结果- 属性访问器缓存是反射性能优化的经典方案
项目落地视角
- 把示例改成最小可运行样例,并观察编译输出、运行结果和异常行为。
- 如果它会进入团队代码规范,最好同步补充命名约定、禁用场景和替代方案。
- 涉及性能结论时,优先用 Benchmark 或实际热点链路验证,而不是凭感觉判断。
常见误区
- 在 EF Core 查询中使用
Expression.Invoke— EF Core 不支持,会导致客户端求值 - 每次调用都
Compile()— 编译开销大,应该缓存 - 把表达式树用于序列化 — 表达式树不容易序列化,考虑其他方案
- 忽略 null 检查 — 动态构建表达式时,属性值可能为 null
进阶路线
- 学习如何为 EF Core 编写自定义的
IMethodCallTranslator - 了解
IQueryable和IQueryProvider的工作机制 - 探索 Source Generator 作为表达式树的替代方案
- 研究
Dynamic LINQ库的实现原理
适用场景
- 动态构建数据库查询条件(ORM、报表系统)
- 反射性能优化(属性访问器缓存、动态工厂)
- 规则引擎(动态组合条件表达式)
- 代码分析和转换工具
落地建议
- 先写最小可运行样例,再把结论迁移到真实业务代码。
- 同时记录这个特性的收益、限制和替代方案,避免为了"高级"而使用。
- 涉及内存、并发或序列化时,最好配合调试器或基准测试验证。
排错清单
- 先确认问题属于编译期、运行期还是语义误用。
- 检查是否存在隐式转换、装箱拆箱、闭包捕获或上下文切换等隐藏成本。
- 查看异常栈、日志和最小复现代码,优先排除使用姿势问题。
复盘问题
- 如果把表达式树进阶放进当前项目,最先要验证的输入、输出和失败路径分别是什么?
- 表达式树最容易在什么规模、什么边界条件下暴露问题?
- 相比默认实现或替代方案,采用表达式树最大的收益和代价分别是什么?
深度实战:动态排序表达式
在业务系统中,前端经常需要按不同字段动态排序。利用表达式树可以安全地构建动态排序逻辑。
using System.Linq.Expressions;
using System.Reflection;
public static class DynamicSortBuilder
{
// 构建动态 OrderBy / OrderByDescending 表达式
public static IQueryable<T> ApplySort<T>(
this IQueryable<T> query, string sortBy, bool descending = false)
{
var param = Expression.Parameter(typeof(T), "x");
var property = Expression.Property(param, sortBy);
var lambda = Expression.Lambda(property, param);
var methodName = descending ? "OrderByDescending" : "OrderBy";
var method = typeof(Queryable).GetMethods()
.Where(m => m.Name == methodName && m.GetParameters().Length == 2)
.Single()
.MakeGenericMethod(typeof(T), property.Type);
var result = method.Invoke(null, new object[] { query, lambda });
return (IQueryable<T>)result!;
}
// 支持多字段排序:"Name ASC, Age DESC"
public static IQueryable<T> ApplyMultiSort<T>(
this IQueryable<T> query, string sortString)
{
if (string.IsNullOrWhiteSpace(sortString))
return query;
var parts = sortString.Split(',');
IOrderedQueryable<T>? ordered = null;
foreach (var part in parts)
{
var segments = part.Trim().Split(' ');
var field = segments[0];
var desc = segments.Length > 1 &&
segments[1].Equals("DESC", StringComparison.OrdinalIgnoreCase);
if (ordered == null)
{
ordered = (IOrderedQueryable<T>)ApplySort<T>(query, field, desc);
}
else
{
ordered = ApplyThenSort(ordered, field, desc);
}
}
return ordered ?? query;
}
private static IOrderedQueryable<T> ApplyThenSort<T>(
IOrderedQueryable<T> query, string sortBy, bool descending)
{
var param = Expression.Parameter(typeof(T), "x");
var property = Expression.Property(param, sortBy);
var lambda = Expression.Lambda(property, param);
var methodName = descending ? "ThenByDescending" : "ThenBy";
var method = typeof(Queryable).GetMethods()
.Where(m => m.Name == methodName && m.GetParameters().Length == 2)
.Single()
.MakeGenericMethod(typeof(T), property.Type);
return (IOrderedQueryable<T>)method.Invoke(null, new object[] { query, lambda })!;
}
}
// 使用示例
var users = dbContext.Users.AsQueryable()
.ApplyMultiSort("IsActive DESC, Name ASC, Age DESC");深度实战:类型转换与 Null 安全表达式
动态构建表达式时,需要处理类型转换、Nullable 类型和 null 检查。下面是一个更健壮的谓词构建器。
using System.Linq.Expressions;
public static class SafePredicateBuilder
{
// 安全的属性访问 — 自动处理 Nullable 类型和 null 检查
public static Expression<Func<T, bool>> BuildSafePredicate<T>(
string propertyName, object value, string op)
{
var param = Expression.Parameter(typeof(T), "x");
var property = Expression.Property(param, propertyName);
var propertyType = property.Type;
var underlyingType = Nullable.GetUnderlyingType(propertyType) ?? propertyType;
// 处理 Nullable 类型
Expression left = property;
if (propertyType != underlyingType)
{
// Nullable<T> → 先取 Value
left = Expression.Property(property, "Value");
}
// 类型转换
var convertedValue = Convert.ChangeType(value, underlyingType);
var constant = Expression.Constant(convertedValue, underlyingType);
Expression body = op switch
{
"==" => Expression.Equal(left, constant),
"!=" => Expression.NotEqual(left, constant),
">" => Expression.GreaterThan(left, constant),
"<" => Expression.LessThan(left, constant),
">=" => Expression.GreaterThanOrEqual(left, constant),
"<=" => Expression.LessThanOrEqual(left, constant),
"contains" => Expression.Call(left, "Contains", null, constant),
"startswith" => Expression.Call(left, "StartsWith", null, constant),
_ => throw new NotSupportedException($"不支持的操作符: {op}")
};
// 如果属性是 Nullable,添加 HasValue 检查
if (propertyType != underlyingType)
{
var hasValue = Expression.Property(property, "HasValue");
body = Expression.AndAlso(hasValue, body);
}
return Expression.Lambda<Func<T, bool>>(body, param);
}
// 多条件组合(AND / OR)
public static Expression<Func<T, bool>> BuildMultiCondition<T>(
List<(string field, string op, object value)> conditions,
bool useAnd = true)
{
if (conditions.Count == 0)
return _ => true;
var param = Expression.Parameter(typeof(T), "x");
Expression? combined = null;
foreach (var (field, op, value) in conditions)
{
var predicate = BuildSafePredicate<T>(field, value, op);
var body = new ParameterReplacer(predicate.Parameters[0], param)
.Visit(predicate.Body)!;
combined = combined == null
? body
: (useAnd ? Expression.AndAlso(combined, body) : Expression.OrElse(combined, body));
}
return Expression.Lambda<Func<T, bool>>(combined!, param);
}
}
// 使用
var filter = SafePredicateBuilder.BuildMultiCondition<User>(new()
{
("Age", ">=", 18),
("Name", "contains", "张"),
("IsActive", "==", true)
});
var users = dbContext.Users.Where(filter).ToList();表达式树与反射的性能对比
using System.Diagnostics;
using System.Linq.Expressions;
using System.Reflection;
// 对比:反射 vs 表达式树编译 vs 直接调用
public class AccessorBenchmark
{
public static void Run()
{
var user = new User { Name = "测试用户", Age = 30 };
const int iterations = 1_000_000;
// 1. 反射 GetProperty
var propInfo = typeof(User).GetProperty("Name")!;
var sw = Stopwatch.StartNew();
for (int i = 0; i < iterations; i++)
{
var val = propInfo.GetValue(user);
}
sw.Stop();
Console.WriteLine($"反射 GetValue: {sw.ElapsedMilliseconds} ms ({iterations:N0} 次)");
// 2. 表达式树编译
var param = Expression.Parameter(typeof(object), "obj");
var cast = Expression.Convert(param, typeof(User));
var property = Expression.Property(cast, "Name");
var convert = Expression.Convert(property, typeof(object));
var getter = Expression.Lambda<Func<object, object?>>(convert, param).Compile();
sw.Restart();
for (int i = 0; i < iterations; i++)
{
var val = getter(user);
}
sw.Stop();
Console.WriteLine($"表达式树编译: {sw.ElapsedMilliseconds} ms ({iterations:N0} 次)");
// 3. 直接调用
sw.Restart();
for (int i = 0; i < iterations; i++)
{
var val = user.Name;
}
sw.Stop();
Console.WriteLine($"直接调用: {sw.ElapsedMilliseconds} ms ({iterations:N0} 次)");
// 典型结果:
// 反射 GetValue: ~800 ms
// 表达式树编译: ~30 ms
// 直接调用: ~5 ms
}
}ExpressionVisitor 高级用法:表达式重写
using System.Linq.Expressions;
// 将 == 比较替换为 Equals 方法调用(处理值类型的 null 比较)
public class EqualityToEqualsRewriter : ExpressionVisitor
{
protected override Expression VisitBinary(BinaryExpression node)
{
// 重写:将 string == string 替换为 string.Equals(string, StringComparison)
if (node.NodeType == ExpressionType.Equal
&& node.Left.Type == typeof(string)
&& node.Right.Type == typeof(string))
{
var equalsMethod = typeof(string).GetMethod(
"Equals",
new[] { typeof(string), typeof(StringComparison) })!;
var comparison = Expression.Constant(StringComparison.OrdinalIgnoreCase);
return Expression.Call(equalsMethod, node.Left, node.Right, comparison);
}
return base.VisitBinary(node);
}
}
// 将表达式中的常量评估为内联值(常量折叠)
public class ConstantFolder : ExpressionVisitor
{
protected override Expression VisitBinary(BinaryExpression node)
{
// 先访问子节点
var left = Visit(node.Left);
var right = Visit(node.Right);
// 如果两边都是常量,在编译期计算
if (left is ConstantExpression leftConst
&& right is ConstantExpression rightConst)
{
var value = node.NodeType switch
{
ExpressionType.Add => (int)leftConst.Value! + (int)rightConst.Value!,
ExpressionType.Subtract => (int)leftConst.Value! - (int)rightConst.Value!,
ExpressionType.Multiply => (int)leftConst.Value! * (int)rightConst.Value!,
ExpressionType.Divide => (int)leftConst.Value! / (int)rightConst.Value!,
_ => throw new NotSupportedException()
};
return Expression.Constant(value);
}
return node.Update(left, node.Conversion, right);
}
}
// 使用常量折叠
Expression<Func<int, int>> expr = x => (3 + 5) * x + (10 - 2);
var folded = (Expression<Func<int, int>>)new ConstantFolder().Visit(expr);
// 折叠后等价于 x => 8 * x + 8
Console.WriteLine(folded); // x => ((8 * x) + 8)Expression<T> 与 Func<T> 的隐式转换陷阱
// 注意:Expression<Func<T>> 和 Func<T> 之间没有隐式转换
// 以下代码无法编译:
// Expression<Func<int, bool>> expr = x => x > 0;
// Func<int, bool> func = expr; // 编译错误!
// 正确方式:
Expression<Func<int, bool>> expr = x => x > 0;
Func<int, bool> func = expr.Compile(); // 需要显式调用 Compile()
// 反过来:Func 不能自动转 Expression
Func<int, bool> func2 = x => x > 0;
// Expression<Func<int, bool>> expr2 = func2; // 编译错误!
// Lambda 表达式的类型由变量声明决定:
var func3 = x => x > 0; // 编译为 Func<int, bool>(委托)
Expression<Func<int, bool>> expr3 = x => x > 0; // 编译为表达式树
// 方法组不能转换为表达式树:
// Expression<Func<User, string>> expr4 = User.GetName; // 编译错误!
// 解决方案:使用 lambda 包装
Expression<Func<User, string>> expr5 = u => u.GetName();