贝利信息

c# Expression.Compile() 的性能开销和并发缓存

日期:2026-01-22 00:00 / 作者:幻夢星雲
Expression.Compile() 不应频繁调用,因其每次都会触发IL生成、JIT编译和委托创建,导致高CPU、GC压力及内存泄漏;正确做法是通过结构化key在ConcurrentDictionary或IMemoryCache中缓存编译结果。

Expression.Compile() 为什么不能每次都调用

每次调用 Expression.Compile() 都会触发 IL 生成、JIT 编译和委托创建,底层涉及 DynamicMethodAssemblyBuilder(.NET Core/5+ 默认走 DynamicMethod),实际开销远高于普通方法调用。在高并发场景下,反复编译同一表达式会导致 CPU 突增、GC 压力上升,甚至触发 JIT 线程争用。

典型错误模式是:在循环里或高频请求中对相同结构的 Expression> 反复 Compile(),比如动态构建查询条件时没做缓存。

手动缓存 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.Compile() 在 ASP.NET Core 中的常见误用

在控制器或中间件里动态构建并编译表达式(如基于查询字符串生成过滤器),极易成为性能瓶颈点。框架本身不帮你缓存,全靠自己设计生命周期。

替代方案:Expression.Compile() 的轻量级选项

如果只是需要“类似委托”的调用能力,且表达式结构固定,

可考虑绕过编译:

真正复杂的动态逻辑(如用户自定义规则引擎),建议直接上 Roslyn 编译 C# 字符串为程序集,虽然启动慢,但执行期零开销,且可卸载。