Compiling a lambda expression results in delegate

2020-02-09 08:11发布

问题:

When I use Expression.Lambda( ... ).Compile() in order to create a delegate from an expression tree, the result is a delegate of which the first argument is Closure.

public static Func<T, T, T> CreateTest<T>()
{
    ParameterExpression a = Expression.Parameter( typeof( T ) );
    ParameterExpression b = Expression.Parameter( typeof( T ) );
    Expression addition = Expression.Add( a, b );

    return (Func<T, T, T>)Expression.Lambda( addition, a, b ).Compile();
}

...

// 'addition' equals
// Int32 lambda_method(
//     System.Runtime.CompilerServices.Closure,
//     Int32,
//     Int32 )
Func<int, int, int> addition = DelegateHelper.CreateTest<int>();
int result = addition( 5, 5 );

I can easily call the delegate through ordinary code without passing a Closure object, but where does this Closure come from?

How can I call this delegate dynamically?

// The following does not work.
// Exception: MethodInfo must be a runtime MethodInfo object.    
MethodInfo additionMethod = addition.Method;
int result = (int)additionMethod.Invoke( null, new object[] { 5, 5 } );

Using expression trees it looks like I have to pass the Closure object.

PropertyInfo methodProperty
    = typeof( Delegate ).GetProperty( "Method", typeof( MethodInfo ) );
MemberExpression getDelegateMethod
    = Expression.Property( Expression.Constant( addition ), methodProperty );
Func<MethodInfo> getMethodInfo
    = (Func<MethodInfo>)Expression.Lambda( getDelegateMethod ).Compile();
// Incorrect number of arguments supplied for call to method
// 'Int32 lambda_method(System.Runtime.CompilerServices.Closure, Int32, Int32)'
Expression call
    = Expression.Call(
        getMethodInfo(),
        Expression.Constant( 5 ), Expression.Constant( 5 ) );

This is a simplified example which doesn't make sense in its own right. What I am actually trying to achieve is to be able to wrap e.g. Func<Action<SomeObject>> with Func<Action<object>>. I can already do this for non nested delegates. This is useful during reflection, as discussed here.

How should I correctly initialize this Closure object, or how do I prevent it from being there?

回答1:

The Closure type you see is an implementation detail. The MSDN is pretty explicit about it:

This API supports the .NET Framework infrastructure and is not intended to be used directly from your code. Represents the runtime state of a dynamically generated method.

An expression tree can have a state.

The Closure instance will contain all the non literal constants that the lambda expression, well, closes over. It can also contain a chain of delegates for nested lambdas in expression trees.

To achieve this, the expression tree compiler uses a cute little trick. It generates in memory code using a DynamicMethod, that is by definition static. Yet, they're creating a delegate that is “closed over its first argument”. Meaning that the CLR will pass the target field of the delegate as a first argument of the static method, so you don't have to. Effectively hiding the Closure argument from you.

The solution to your problem is simple, don't try to call the method, invoke the delegate, either by using Delegate.DynamicInvoke when you're using reflection, or Expression.Invoke in the context of an expression tree.