Compilar los resultados de una expresión lambda en delegado con argumento de cierre

c# expression-trees lambda

Pregunta

Cuando uso Expression.Lambda( ... ).Compile() para crear un delegado a partir de un árbol de expresiones, el resultado es un delegado cuyo primer argumento es 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 );

Puedo llamar fácilmente al delegado a través de un código ordinario sin pasar un objeto de Closure , pero ¿de dónde proviene este Closure ?

¿Cómo puedo llamar a este delegado dinámicamente?

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

Usando árboles de expresiones parece que tengo que pasar el objeto 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 ) );

Este es un ejemplo simplificado que no tiene sentido por derecho propio. Lo que realmente estoy tratando de lograr es poder envolver, por ejemplo, Func<Action<SomeObject>> con Func<Action<object>> . Ya puedo hacer esto para los delegados no anidados. Esto es útil durante la reflexión, como se discute aquí .

¿Cómo debo inicializar correctamente este objeto de Closure , o cómo puedo evitar que esté allí?

Respuesta aceptada

El tipo de Closure que ve es un detalle de implementación. El MSDN es bastante explícito al respecto:

Esta API es compatible con la infraestructura de .NET Framework y no está diseñada para ser utilizada directamente desde su código. Representa el estado de ejecución de un método generado dinámicamente.

Un árbol de expresiones puede tener un estado.

La instancia de cierre contendrá todas las constantes no literales que la expresión lambda, bueno, cierra. También puede contener una cadena de delegados para lambdas anidadas en árboles de expresión.

Para lograr esto, el compilador del árbol de expresiones usa un pequeño truco. Genera en código de memoria usando un DynamicMethod , que es por definición estático. Sin embargo, están creando un delegado que está "cerrado sobre su primer argumento" . Lo que significa que el CLR pasará el campo de destino del delegado como primer argumento del método estático, por lo que no tiene que hacerlo. Ocultando efectivamente el argumento de cierre de ti.

La solución a su problema es simple, no intente llamar al método, invoque al delegado, ya sea utilizando Delegate.DynamicInvoke cuando use reflexión, o Expression.Invoke en el contexto de un árbol de expresiones.



Licencia bajo: CC-BY-SA with attribution
No afiliado con Stack Overflow
¿Es esto KB legal? Sí, aprende por qué
Licencia bajo: CC-BY-SA with attribution
No afiliado con Stack Overflow
¿Es esto KB legal? Sí, aprende por qué