编译的 lambda 表达式会导致新的委托分配,而非表达式版本不会

问题描述 投票:0回答:1

这个编译好的表达式树...

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
时创建一个新委托 - 如果例如在
Delegate.CreateDelegateNoSecurityCheck
中设置断点,则可观察到。

相比之下,该函数的基于非表达式的等价物

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

似乎没有这样做;重复调用

outer
不需要任何新的委托分配。

我很难理解为什么。这是故意的吗?是否有任何巧妙的技巧可以缓存基于表达式的版本的委托?


对于上下文:这是在使用外部反序列化库时出现的,该库似乎通过在我们的进程中创建委托来分配不合理的内存量。实际上,它做了非常相似的事情 - 它通过表达式树创建反序列化器,并将委托分配给局部变量以支持递归和循环反序列化。

c# delegates expression expression-trees
1个回答
0
投票

此处的选项之一是编译内部 lambda 并调用它:

var compile = inner.Compile();
var method = compile.GetType().GetMethod(nameof(Action<int>.Invoke));
var outerExpression = Expression.Lambda<Action<int>>(Expression.Call(Expression.Constant(compile), method, param), param);
var outer = outerExpression.Compile();

var totalAllocatedBytes = GC.GetTotalAllocatedBytes();

for (int i = 0; i < 1000_000; i++)
{
    outer(1);
}

Console.WriteLine(GC.GetTotalAllocatedBytes() - totalAllocatedBytes);
© www.soinside.com 2019 - 2024. All rights reserved.