Expression.Compile() 不应频繁调用,因其每次都会触发IL生成、JIT编译和委托创建,导致高CPU、GC压力及内存泄漏;正确做法是通过结构化key在ConcurrentDictionary或IMemoryCache中缓存编译结果。
每次调用 Expression.Compile() 都会触发 IL 生成、JIT 编译和委托创建,底层涉及 DynamicMethod 或 AssemblyBuilder(.NET Core/5+ 默认走 DynamicMethod),实际开销远高于普通方法调用。在高并发场景下,反复编译同一表达式会导致 CPU 突增、GC 压力上升,甚至触发 JIT 线程争用。
典型错误模式是:在循环里或高频请求中对相同结构的 Expression 反复 Compile(),比如动态构建查询条件时没做缓存。
缓存核心是「键唯一性 + 线程安全」。不能只用 Expression.ToString() 当 key——它不稳定(节点顺序、调试信息可能变),也不反映语义等价性。推荐用表达式结构哈希或标准化后比较。
最简可行方案是用 ConcurrentDictionary,key 由表达式类型 + 关键参数组合生成(例如字段名、操作符、字面值):
private static readonly ConcurrentDictionary> _cache = new(); public static Func
GetFilter(string propertyName, object value) { var key = $"Person_{propertyName}Equals{value}"; return cache.GetOrAdd(key, => { var param = Expression.Parameter(typeof(Person)); var body = Expression.Equal( Expression.Property(param, propertyName), Expression.Constant(value) ); return Expression.Lambda >(body, param).Compile(); }); }
Expression 对象当 key(它没重写 GetHashCode,默认是引用哈希)value 是局部变量),否则闭包会阻止委托被缓存复用Expression.Constant(new object()),该对象每次新建,导致 key 不一致——应提前提取为静态只读字段在控制器或中间件里动态构建并编译表达式(如基于查询字符串生成过滤器),极易成为性能瓶颈点。框架本身不帮你缓存,全靠自己设计生命周期。
Microsoft.Extensions.Caching.Memory.IMemoryCache 时,key 必须可序列化且稳定;建议用 MemoryCacheEntryOptions.SlidingExpiration 防止无限增长Where(expression) 内部已缓存编译结果(针对相同表达式树结构),但仅限于 EF 自己解析的子集;手写复杂表达式仍需自行管理如果只是需要“类似委托”的调用能力,且表达式结构固定,

Expression.Evaluate()(需引入 System.Linq.Expressions.Extensions 第三方包)——解释执行,无编译开销,适合低频、调试场景static readonly Expression> EqualTo = ... ),运行时替换参数节点再编译,减少重复生成Expression.CompileFast()(非官方 API,来自 FastExpressionCompiler 库),比原生快 2–5 倍,且支持更广的表达式语法真正复杂的动态逻辑(如用户自定义规则引擎),建议直接上 Roslyn 编译 C# 字符串为程序集,虽然启动慢,但执行期零开销,且可卸载。