2011-08-28 7 views
14

Estoy aprendiendo árboles de expresión en C#.Variable local y árboles de expresión

estoy atascado ahora por un tiempo:

string filterString = "ruby"; 
Expression<Func<string, bool>> expression = x => x == filterString; 

¿Cómo puedo construir esta expresión por código? No hay una muestra de cómo capturar una variable local. Esta es fácil:

Expression<Func<string, bool>> expression = x => x == "ruby"; 

Esto sería:

ParameterExpression stringParam = Expression.Parameter(typeof(string), "x"); 
Expression constant = Expression.Constant("ruby"); 
BinaryExpression equals = Expression.Equal(stringParam, constant); 
Expression<Func<string, bool>> lambda1 = 
    Expression.Lambda<Func<string, bool>>(
     equals, 
     new ParameterExpression[] { stringParam }); 

Las impresiones de depuración para el siguiente (x => x == FilterString):

{x => (x == valor (Predicate.Program + <> c__DisplayClass3) .filterString)}

Gracias por arrojar algo de luz sobre este tema.

Respuesta

24

Captura de una variable local es efectuado por "elevación" la variable local en un variable de instancia de una clase generado por el compilador. El compilador de C# crea una nueva instancia de la clase extra en el momento apropiado y cambia cualquier acceso a la variable local en un acceso de la variable de instancia en la instancia relevante.

Por lo tanto, el árbol de expresión debe ser un acceso de campo dentro de la instancia, y la instancia en sí se proporciona a través de un ConstantExpression.

El enfoque más simple para trabajar cómo crear árboles de expresiones es crear algo similar en una expresión lambda, luego mirar el código generado en Reflector, bajando el nivel de optimización para que Reflector no lo convierta de nuevo a lambda expresiones.

+2

Gracias. La sugerencia para ver el código MSIL generado es muy útil. – yonexbat

+3

Algo como 'var hoistedConstant = Expression.Property (Expression.Constant (new {Value = filterString})," Value ");' debería hacerlo – Appetere

+1

@Appetere ¿Qué pasa con 'Expression.Constant (filterString)'? Es cierto que no reflejaría cambios en la variable, pero tampoco su sugerencia. –

5

Este código ajusta la expresión en un bloque de cierre que trata la variable local como una constante.

string filterString = "ruby"; 

var filterStringParam = Expression.Parameter(typeof(string), "filterString"); 
var stringParam = Expression.Parameter(typeof(string), "x"); 

var block = Expression.Block(
// Add a local variable. 
new[] { filterStringParam }, 
// Assign a constant to the local variable: filterStringParam = filterString 
Expression.Assign(filterStringParam, Expression.Constant(filterString, typeof(string))), 
// Compare the parameter to the local variable 
Expression.Equal(stringParam, filterStringParam)); 

var x = Expression.Lambda<Func<string, bool>>(block, stringParam).Compile(); 
0

Una vieja pregunta, pero llegué a ella cuando se trata de hacer algo generar expresiones similares para LINQ-a-entidades (L2e) En ese caso no se puede utilizar Expression.Block, ya que no se puede analizar a SQL.

Aquí hay un ejemplo explícito de la respuesta de Jon que funcionaría con L2E. Crear una clase de ayuda a contener el valor del filtro:

class ExpressionScopedVariables 
{ 
    public String Value; 
} 

construir el árbol de este modo:

var scope = new ExpressionScopedVariables { Value = filterString}; 
var filterStringExp = Expression.Constant(scope); 
var getVariable = typeof(ExpressionScopedVariables).GetMember("Value")[0]; 
var access = Expression.MakeMemberAccess(filterStringExp, getVariable); 

Y luego vuelva a colocar la constante en el código original con la expresión del acceso de miembros:

BinaryExpression equals = Expression.Equal(stringParam, access); 
Expression<Func<string, bool>> lambda1 = 
    Expression.Lambda<Func<string, bool>>(
     equals, 
     new ParameterExpression[] { stringParam }); 
Cuestiones relacionadas