Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
681 views
in Technique[技术] by (71.8m points)

c# - Compiling a lambda expression results in delegate with Closure argument

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?

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

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.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...