2008-12-06 6 views
12

Tengo un árbol de expresiones que he creado al analizar un Xml usando la clase de expresión en C#. See this question.¿Cómo compilo un árbol de expresiones en un método invocable, C#?

Solo tengo Agregar, Restar, Dividir, Multiplicar, Parámetros, Y y O en mi Árbol de Expresión. ¿Hay alguna manera de convertir este ExpressionTree en un método invocable? ... o tengo que emitir el IL manualmente?

Saludos cordiales,

+2

Ejemplos adicionales agregados ... –

Respuesta

10

Aquí hay un ejemplo de ambos enfoques. Si me he perdido algo, o si desea más información, solo hágamelo saber.

static void Main() 
{ 
    // try to do "x + (3 * x)" 

    var single = BuildSingle<decimal>(); 
    var composite = BuildComposite<decimal>(); 

    Console.WriteLine("{0} vs {1}", single(13.2M), composite(13.2M)); 
} 
// utility method to get the 3 as the correct type, since there is not always a "int x T" 
static Expression ConvertConstant<TSource, TDestination>(TSource value) 
{ 
    return Expression.Convert(Expression.Constant(value, typeof(TSource)), typeof(TDestination)); 
} 
// option 1: a single expression tree; this is the most efficient 
static Func<T,T> BuildSingle<T>() 
{   
    var param = Expression.Parameter(typeof(T), "x"); 
    Expression body = Expression.Add(param, Expression.Multiply(
     ConvertConstant<int, T>(3), param)); 
    var lambda = Expression.Lambda<Func<T, T>>(body, param); 
    return lambda.Compile(); 
} 
// option 2: nested expression trees: 
static Func<T, T> BuildComposite<T>() 
{ 

    // step 1: do the multiply: 
    var paramInner = Expression.Parameter(typeof(T), "inner"); 
    Expression bodyInner = Expression.Multiply(
     ConvertConstant<int, T>(3), paramInner); 
    var lambdaInner = Expression.Lambda(bodyInner, paramInner); 

    // step 2: do the add, invoking the existing tree 
    var paramOuter = Expression.Parameter(typeof(T), "outer"); 
    Expression bodyOuter = Expression.Add(paramOuter, Expression.Invoke(lambdaInner, paramOuter)); 
    var lambdaOuter = Expression.Lambda<Func<T, T>>(bodyOuter, paramOuter); 

    return lambdaOuter.Compile(); 
} 

Personalmente, apuntar hacia el primer método; es más simple y más eficiente. Esto podría implicar pasar el parámetro original a través de una pila de código anidado, pero que así sea. Tengo un código en alguna parte que toma el enfoque "Invocar" (compuesto) y vuelve a escribir el árbol como primer enfoque (único), pero es bastante complejo y largo. Pero es muy útil para Entity Framework (que no es compatible con Expression.Invoke).

+0

Eso funcionó ... Muchas gracias. –

12

Se necesitan crear una lambda - es decir,

var lambda = Expression.Lambda<Func<float,int>>(body, param); 
Func<float,int> method = lambda.Compile(); 
int v = method(1.0); // test 

donde "cuerpo" es su árbol de expresión (teniendo un flotador, que devuelve un int) que implica el parámetro ParameterExpression.

También puede encontrar this y this útil.

+0

El problema es ((x + 2) + y)/z Cuando hay más de un parámetro como parte de diferentes sub expresiones en el árbol, ¿qué debo hacer? –

+0

Son posibles varios parámetros (es una matriz params de ParameterExpression); para las subexpresiones, necesita invocar la expresión interna (Expression.Invoke?) –

+0

También puede construir el árbol completo en una sola expresión; esto es un poco más eficiente, pero es más complejo de hacer. –

Cuestiones relacionadas