2012-06-22 7 views
9

Teniendo en cuenta este código:Reemplazar parámetro en la expresión lambda

public class Foo 
{ 
    public int a { get; set; } 
    public int b { get; set; } 
} 

private void Test() 
{ 
    List<Foo> foos = new List<Foo>(); 
    foos.Add(new Foo()); 
    foos.Add(new Foo()); 
    Expression<Func<Foo, int>> exp0 = f => f.a * f.b; 
    Expression<Func<int>> exp1 =() => foos[0].a * foos[0].b; 
    Expression<Func<int>> exp2 =() => foos[1].a * foos[1].b; 
} 

¿Cómo se puede tomar exp0 y convertirla en dos expresiones idénticas a exp1 y exp2. Tenga en cuenta que no quiero simplemente evaluar exp0 para cada Foo en foos, sino que en cambio obtener dos expresiones nuevas.

[Actualización]:

Básicamente, quiero ser capaz de expandirse o "aplanar" una expresión pasó a un método Linq extensión, como Sum en una expresión por cada elemento de la enumeración ya que estas enumeraciones se ser estático, y porque ya tengo un código que lee expresiones que no toman parámetros (y luego los convierte en otro idioma).

estoy usando el MetadataToken como referencias a las propiedades que tienen un determinado atributo (en este caso a y b tendría este atributo) y usarlo con un diccionario que se correlaciona C# propiedades de las variables de otro idioma:

Foo foo = new Foo(); 
Expression<Func<int>> exp =() => foo.a * foo.a + foo.b; 
string result1 = GetResult(exp); // gets "v_001 * v_001 + v_002" 

List<Foo> foes = new List<Foo>(); 
foes.Add(new Foo()); 
foes.Add(new Foo()); 
Expression<Func<int>> exp2 =() => foes.Sum(f => f.a * f.a + f.b); 
string result2 = GetResult(exp2); // should get "(v_001 * v_001 + v_002) + (v_003 * v_003 + v_004)" 
+0

Puede darnos una ejemplo de cómo usarías esto y cuál sería el resultado? –

+0

Ver también http://stackoverflow.com/questions/5631070/currying-expressions-in-c-sharp – AakashM

Respuesta

15

lo haría de esta manera:

Escribir una expresión visitante parámetro sustituto que manipula la expresión original de la siguiente manera:

  1. Deshace el parámetro que no quiere del todo de la firma lambda.
  2. Reemplaza todos los usos del parámetro con la expresión del indexador deseada.

He aquí una muestra rápida y sucia me prepararon rápidamente en base a mi earlier answer en una pregunta diferente:

public static class ParameterReplacer 
{ 
    // Produces an expression identical to 'expression' 
    // except with 'source' parameter replaced with 'target' expression.  
    public static Expression<TOutput> Replace<TInput, TOutput> 
        (Expression<TInput> expression, 
        ParameterExpression source, 
        Expression target) 
    { 
     return new ParameterReplacerVisitor<TOutput>(source, target) 
        .VisitAndConvert(expression); 
    } 

    private class ParameterReplacerVisitor<TOutput> : ExpressionVisitor 
    { 
     private ParameterExpression _source; 
     private Expression _target; 

     public ParameterReplacerVisitor 
       (ParameterExpression source, Expression target) 
     { 
      _source = source; 
      _target = target; 
     } 

     internal Expression<TOutput> VisitAndConvert<T>(Expression<T> root) 
     { 
      return (Expression<TOutput>)VisitLambda(root); 
     } 

     protected override Expression VisitLambda<T>(Expression<T> node) 
     { 
      // Leave all parameters alone except the one we want to replace. 
      var parameters = node.Parameters 
           .Where(p => p != _source); 

      return Expression.Lambda<TOutput>(Visit(node.Body), parameters); 
     } 

     protected override Expression VisitParameter(ParameterExpression node) 
     { 
      // Replace the source with the target, visit other params as usual. 
      return node == _source ? _target : base.VisitParameter(node); 
     } 
    } 
} 

Uso para su escenario (probado):

var zeroIndexIndexer = Expression.MakeIndex 
     (Expression.Constant(foos), 
     typeof(List<Foo>).GetProperty("Item"), 
     new[] { Expression.Constant(0) }); 


// .ToString() of the below looks like the following: 
// () => (value(System.Collections.Generic.List`1[App.Foo]).Item[0].a 
//   * value(System.Collections.Generic.List`1[App.Foo]).Item[0].b) 
var exp1Clone = ParameterReplacer.Replace<Func<Foo, int>, Func<int>> 
        (exp0, exp0.Parameters.Single(), zeroIndexIndexer); 
+1

Para lambdas anidados, encontré debo utilizar Expression.Lambda (...) en lugar de Expression.Lambda (. ..). La versión del parámetro de tipo parece innecesaria en cualquier caso. – HappyNomad

Cuestiones relacionadas