Компиляция лямбда-выражения приводит к делегату с аргументом Closure - PullRequest
13 голосов
/ 29 октября 2011

Когда я использую Expression.Lambda( ... ).Compile() для создания делегата из дерева выражений, результатом является делегат, первым аргументом которого является 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 );

Я могу легко вызвать делегата через обычный код, не передавая объект Closure, но откуда это Closure взято?

Как я могу вызвать этого делегата динамически?

// 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 } );

Используя деревья выражений, я должен передать объект Closure.

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 ) );

Это упрощенный пример, который сам по себе не имеет смысла. То, чего я на самом деле пытаюсь достичь, - это уметь обернуть, например. Func<Action<SomeObject>> с Func<Action<object>>. Я уже могу сделать это для не вложенных делегатов. Это полезно во время размышления, , как обсуждено здесь .

Как правильно инициализировать этот Closure объект или как я могу предотвратить его появление?

1 Ответ

12 голосов
/ 29 октября 2011

Тип Closure, который вы видите, является подробностью реализации. MSDN довольно явно говорит об этом:

Этот API поддерживает инфраструктуру .NET Framework и не является предназначен для использования непосредственно из вашего кода. Представляет время выполнения состояние динамически генерируемого метода.

Дерево выражений может иметь состояние.

Экземпляр Closure будет содержать все не-литеральные константы, которые закрывает лямбда-выражение. Он также может содержать цепочку делегатов для вложенных лямбд в деревьях выражений.

Чтобы добиться этого, компилятор дерева выражений использует симпатичный маленький трюк. Он генерирует в памяти код, используя DynamicMethod, то есть по определению статический. Тем не менее, они создают делегата, который «закрыт по своему первому аргументу» . Это означает, что CLR передаст поле назначения делегата в качестве первого аргумента статического метода, поэтому вам не нужно это делать. Эффективно скрывая от вас аргумент о закрытии.

Решение вашей проблемы простое, не пытайтесь вызвать метод, вызовите делегат, используя Delegate.DynamicInvoke , когда вы используете отражение, или Expression.Invoke в контексте дерева выражений.

...