This compiled expression tree…
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();
Appears to lead to the creation of a new delegate on every single invocation of outer
– observable if eg setting a breakpoint in Delegate.CreateDelegateNoSecurityCheck
.
In contrast, the non-expression based equivalent of this function
Action<int> inner = _ => { };
Action<int> outer = x =>
{
Action<int> innerParam = inner;
innerParam(x);
};
does not appear to do this; repeated invocations of outer
do not need to any new delegate allocations.
I struggle do understand why. Is this intended? Is there any nifty trick to have the expression-based version’s delegates cached?
For context: This came up when using an external Deserialization library that seemed to allocate an unreasonable amount of memory through Delegate creation in our process. In effect, it does something very similar – it creates deserializers via Expression trees, and assigns delegates to local variables to support recursive & circular deserialization.