2013-10-03 5 views
1

Je ne suis pas sûr si c'est strictement currying, mais je veux essentiellement réaliser ce qui suit. Étant donné un Expression:Comment puis-je convertir une expression en une autre expression?

Expression<Func<T1, T2, TResult>> expression 

Je veux passer un des arguments et de produire un Expression correspondant où la valeur de ce paramètre est fixe. L'expression résultante doit être fonctionnellement équivalente à expression sauf qu'elle doit contenir un paramètre de moins.

Cette expression résultante ressemblerait à quelque chose comme ceci:

Expression<Func<T2, TResult>> curriedExpression; 

J'ai essayé, mais cela ne fonctionne pas parce qu'un Expression ne convertit pas implicitement à une expression lambda:

curriedExpression = b => expression(fixedValueForT1, b); 

Notez que curriedExpression ne doit pas contenir d'appel à expression; il devrait contenir une logique dupliquée sauf avec la valeur fixe.

J'espère que cela a du sens. Faites-moi savoir si cela est ambigu ou pas bien expliqué.

Répondre

3

Je pense que vous pouvez simplement dériver de la classe ExpressionVisitor d'une manière simple. Voici une preuve de concept - il peut être trop simpliste, mais je pense que c'est ce que vous êtes après:

using System; 
using System.Linq.Expressions; 

class Test 
{ 
    static void Main() 
    { 
     Expression<Func<int, int, int>> original = (x, y) => MethodX(x) + MethodY(y); 
     Console.WriteLine("Original: {0}", original); 
     var partiallyApplied = ApplyPartial(original, 10); 
     Console.WriteLine("Partially applied: {0}", partiallyApplied); 
    } 

    static int MethodX(int x) 
    { 
     return x + 1; 
    } 

    static int MethodY(int x) 
    { 
     return -x; 
    } 

    static Expression<Func<T2, TResult>> ApplyPartial<T1, T2, TResult> 
     (Expression<Func<T1, T2, TResult>> expression, T1 value) 
    { 
     var parameter = expression.Parameters[0]; 
     var constant = Expression.Constant(value, parameter.Type); 
     var visitor = new ReplacementVisitor(parameter, constant); 
     var newBody = visitor.Visit(expression.Body); 
     return Expression.Lambda<Func<T2, TResult>>(newBody, expression.Parameters[1]); 
    } 
} 

class ReplacementVisitor : ExpressionVisitor 
{ 
    private readonly Expression original, replacement; 

    public ReplacementVisitor(Expression original, Expression replacement) 
    { 
     this.original = original; 
     this.replacement = replacement; 
    } 

    public override Expression Visit(Expression node) 
    { 
     return node == original ? replacement : base.Visit(node); 
    } 
} 

Sortie:

Original: (x, y) => (MethodX(x) + MethodY(y)) 
Partially applied: y => (MethodX(10) + MethodY(y)) 
+0

Merci. Y a-t-il un moyen plus simple qui soit plus proche de la façon dont vous feriez la même chose avec 'Func's? – Sam

+0

Je crains qu'il n'y ait pas de manière plus simple, quand vous travaillez avec des expressions vous devez traiter la structure syntaxique, et c'est toujours une affaire assez compliquée! –

0

Je viens de découvrir que cela est probablement possible en utilisant LinqKit , que vous pouvez obtenir via NuGet here.

Je n'ai pas le temps pour l'instant de l'essayer pour cet exemple, mais il vaut probablement la peine de l'examiner afin de ne pas avoir à utiliser une solution comme ExpressionVisitor.

0

Ceci est une alternative à la mise en œuvre @ jon-pigeon d'argile, avec les avantages/inconvénients suivants:

Plus:

  • L'expression d'entrée peut avoir 0..n arguments de tout type.
  • Vous pouvez annuler l'un de ces paramètres en spécifiant l'index de celui qui a été remplacé.

Moins:

  • Vous perdez le type de compilation sécurité (l'expression d'entrée n'a pas de paramètres génériques, l'indice pourrait être hors de portée et le remplacement est un object).
  • Vous devez spécifier le type de l'expression lambda renvoyée.
private Expression<TLambda> Curry<TLambda>(
    LambdaExpression searchExpression, 
    int replacedParameterIndex, 
    object replacement) 
{ 
    var parameter = searchExpression.Parameters[replacedParameterIndex]; 
    var constant = Expression.Constant(replacement, parameter.Type); 
    var visitor = new ReplacementVisitor(parameter, constant); 
    var newBody = visitor.Visit(searchExpression.Body); 
    var lambda = Expression.Lambda<TLambda>(newBody, searchExpression.Parameters.Except(new[] { parameter })); 

    return lambda; 
} 

Ainsi, dans l'exemple de @ jon-Skeet nous utiliserions:

var partiallyApplied = Curry<int, int>(original, 0, 10); 
Questions connexes