You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

表达式树编译版每次调用分配新委托,普通Lambda则不——原因及缓存方法?

表达式树每次调用生成新委托的原因及缓存方案

问题场景

以下表达式树实现的代码,每次调用outer时都会创建新的委托实例,可以通过在Delegate.CreateDelegateNoSecurityCheck断点观察到这一现象:

var param = Expression.Parameter(typeof(int));
var innerParam = Expression.Parameter(typeof(Action<int>));
var inner = Expression.Lambda(innerParam.Type, Expression.Block(), "test", new[] { param });
var outer = Expression.Lambda<Action<int>>(Expression.Block(new[] { innerParam }, Expression.Assign(innerParam, inner), Expression.Invoke(innerParam, param)), param).Compile();

而对应的非表达式树等效代码,重复调用outer不会产生新的委托分配:

Action<int> outer = x =>
{
  Action<int> innerParam = _ => { };
  innerParam(x);
};

疑问:这是预期行为吗?有没有办法缓存表达式树版本的委托以避免重复分配?

背景:该问题源于使用外部序列化库时,进程因委托重复创建产生了不合理的内存分配——该库通过表达式树创建反序列化器,并将委托分配给局部变量以支持递归和循环反序列化。

原因分析(是预期行为)

  • 非表达式树代码的优化:C#编译器会对无捕获外部变量的lambda表达式(如示例中的_ => {})进行优化,将其编译为静态字段存储的委托实例。每次调用outer时,直接复用这个静态实例,不会重复分配内存。
  • 表达式树的运行时行为:在表达式树实现中,inner是一个LambdaExpression节点。当outer执行到Expression.Assign(innerParam, inner)步骤时,运行时会将该LambdaExpression动态编译为新的委托实例——表达式树运行时不会自动缓存这类动态生成的委托,因此每次执行都会触发新的委托创建。

缓存委托的实现方案

方案1:提前编译并嵌入常量委托

如果内部委托的逻辑固定(无动态依赖),可以提前编译好委托实例,然后用Expression.Constant将其嵌入表达式树,避免每次执行时重新编译:

var param = Expression.Parameter(typeof(int));
// 提前编译内部委托,仅执行一次
var innerDelegate = (Action<int>)(_ => { });
var innerParam = Expression.Parameter(typeof(Action<int>));

var outer = Expression.Lambda<Action<int>>(
    Expression.Block(
        new[] { innerParam },
        // 直接赋值预编译的委托实例
        Expression.Assign(innerParam, Expression.Constant(innerDelegate)),
        Expression.Invoke(innerParam, param)
    ), param).Compile();

方案2:静态字段缓存动态委托

如果内部委托的逻辑依赖外部参数(无法提前编译),可以通过静态字段实现缓存逻辑,在表达式树中加入“先取缓存,无缓存则创建并存储”的分支:

// 先定义用于缓存的静态字段
private static Action<int> _innerDelegateCache;

var param = Expression.Parameter(typeof(int));
var innerParam = Expression.Parameter(typeof(Action<int>));
// 获取缓存字段的反射信息
var cacheField = typeof(YourClass).GetField("_innerDelegateCache", BindingFlags.Static | BindingFlags.NonPublic);

// 构建"获取或创建委托"的表达式
var getOrCreateInner = Expression.Condition(
    Expression.NotEqual(Expression.Field(null, cacheField), Expression.Constant(null)),
    Expression.Field(null, cacheField),
    Expression.Assign(
        Expression.Field(null, cacheField),
        Expression.Lambda(typeof(Action<int>), Expression.Block(), param)
    )
);

var outer = Expression.Lambda<Action<int>>(
    Expression.Block(
        new[] { innerParam },
        Expression.Assign(innerParam, getOrCreateInner),
        Expression.Invoke(innerParam, param)
    ), param).Compile();

注:将YourClass替换为实际包含静态字段的类名。

内容的提问来源于stack exchange,提问作者Bogey

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.20 23:06:09