2011-10-28 16 views
9

Cuando uso Expression.Lambda(...).Compile() para crear un delegado de un árbol de expresiones, el resultado es un delegado cuyo primer argumento es Closure.La compilación de una expresión lambda da como resultado un delegado con el argumento 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 a través del delegado código ordinaria sin pasar un objeto Closure, pero ¿de dónde viene esta Closure viene?

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

El uso de árboles de expresión se ve como si tuviera 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 en sí mismo. Lo que en realidad estoy tratando de lograr es poder envolver, por ejemplo, Func<Action<SomeObject>> con Func<Action<object>>. Ya puedo hacer esto para delegados no anidados. Esto es útil durante la reflexión, as discussed here.

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

+2

¿Podría dar un ejemplo corto pero * completo *? No es realmente obvio cuál es el problema. –

+1

@JonSkeet: Haré mi mejor esfuerzo, el problema es que el ejemplo total es bastante complejo. Intento llamar a un delegado previamente compilado recursivamente. Mientras trato de extraer un pequeño subconjunto del problema, [aquí] (http://pastebin.com/53f0VqnF) ya puede encontrar la función completa. –

+0

Sí, hacerlo más corto sin duda ayudaría :) –

Respuesta

9

El tipo 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á destinado a ser utilizado desde el código. Representa el estado de tiempo 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 anidados en árboles de expresiones.

Para lograr esto, el compilador de árbol de expresiones utiliza un pequeño y lindo truco. Se genera en código de memoria usando DynamicMethod, que es estático por definición. Sin embargo, están creando un delegado que es “closed over its first argument”. 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 es necesario. Efectivamente ocultar el argumento de cierre de usted.

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

+0

Esto es genial, gracias! Mi función funcionaba recursivamente llamándose a sí misma con un nuevo 'MethodInfo'. Creé uno nuevo que acepta un delegado en su lugar y usa 'Expression.Invoke' para llamarlo. Después de algunas refactorizaciones importantes, confirmaré los cambios. Ahora puedo envolver con éxito, p. 'Func >' con 'Func >'. Cálculos genéricos con enums fuertemente tipados. :) Amándolo. 'IEnumerable setFlags = EnumHelper .GetFlaggedValues ​​(flags);' –

+0

K, esto también es posible en .NET 4.0 usando 'Enum', pero puedo usar este 'delegate wrapper' en muchos otros escenarios. Hice una comparación de eficiencia corta con 'Enum' y la diferencia es insignificante. –

Cuestiones relacionadas