2011-03-19 2 views
5

Je souhaite combiner deux LambdaExpressions sans les compiler.Comment créer une LambdaExpression à partir d'une expression LambdaExpression existante sans la compilation

C'est ce qu'il ressemble à si je ne les compiler:

public Expression<Func<TContainer,bool>> CreatePredicate<TContainer,TMember>(
     Expression<Func<TContainer,TMember>> getMemberExpression, 
     Expression<Func<TMember,bool>> memberPredicateExpression) 
    { 
     return x => memberPredicateExpression.Compile()(getMemberExpression.Compile()(x)); 
    } 

C'est évidemment pas le meilleur moyen d'obtenir l'expression cible à partir des arguments fournis. De plus, cela le rend incompatible avec les fournisseurs de requêtes comme LINQ to SQL qui ne supportent pas les appels de méthode C#. De ce que j'ai lu, il semble que la meilleure approche consiste à construire une classe ExpressionVisitor. Cependant, cela semble être une tâche assez courante. Est-ce que quelqu'un sait d'une base de code open source existante qui fournit ce genre de fonctionnalité? Si non, quelle est la meilleure façon d'approcher le ExpressionVisitor pour le rendre aussi générique que possible?

Répondre

4

Je ne sais pas si c'est la meilleure façon, mais vous pouvez faire quelque chose comme ça:

public Expression<Func<TContainer,bool>> CreatePredicate<TContainer,TMember>(
    Expression<Func<TContainer,TMember>> getMemberExpression, 
    Expression<Func<TMember,bool>> memberPredicateExpression) 
{ 
    ParameterExpression x = Expression.Parameter(typeof(TContainer), "x"); 
    return Expression.Lambda<Func<TContainer, bool>>(
     Expression.Invoke(
      memberPredicateExpression, 
      Expression.Invoke(
       getMemberExpression, 
       x)), 
     x); 
} 

Utilisation:

var expr = CreatePredicate(
    (Foo f) => f.Bar, 
    bar => bar % 2 == 0); 

Résultat:

x => Invoke(bar => ((bar % 2) == 0), Invoke(f => f.Bar, x)) 

je suppose il serait préférable d'obtenir quelque chose comme x => x.Bar % 2 == 0, mais il serait probablement beaucoup plus difficile ...


EDIT: en fait il n'a pas été si difficile avec un visiteur d'expression:

public Expression<Func<TContainer,bool>> CreatePredicate<TContainer,TMember>(
    Expression<Func<TContainer,TMember>> getMemberExpression, 
    Expression<Func<TMember,bool>> memberPredicateExpression) 
{ 
    return CombineExpressionVisitor.Combine(
     getMemberExpression, 
     memberPredicateExpression); 
} 

class CombineExpressionVisitor : ExpressionVisitor 
{ 
    private readonly ParameterExpression _parameterToReplace; 
    private readonly Expression _replacementExpression; 
    private CombineExpressionVisitor(ParameterExpression parameterToReplace, Expression replacementExpression) 
    { 
     _parameterToReplace = parameterToReplace; 
     _replacementExpression = replacementExpression; 
    } 

    public static Expression<Func<TSource, TResult>> Combine<TSource, TMember, TResult>(
     Expression<Func<TSource, TMember>> memberSelector, 
     Expression<Func<TMember, TResult>> resultSelector) 
    { 
     var visitor = new CombineExpressionVisitor(
      resultSelector.Parameters[0], 
      memberSelector.Body); 
     return Expression.Lambda<Func<TSource, TResult>>(
      visitor.Visit(resultSelector.Body), 
      memberSelector.Parameters); 
    } 

    protected override Expression VisitParameter(ParameterExpression parameter) 
    { 
     if (parameter == _parameterToReplace) 
      return _replacementExpression; 
     return base.VisitParameter(parameter); 
    } 
} 

Il donne l'expression suivante:

f => ((f.Bar % 2) == 0) 
+0

Merci - Je vais en fait ce que vous dit était «nettement plus difficile». Cela pourrait finir par être un bon point de départ si. – smartcaveman

+0

@Thomas, Okay, c'est ce que je pense: Faites ce que vous avez suggéré, mais comparez d'abord les expressions ParameterExpress de LambdaExpression pour vous assurer qu'il n'y a pas de noms de paramètres en double. Ensuite, demandez à 'ExpressionVisitor' de visiter' ExpressionType.Invoke', en sélectionnant les arguments, et créez un nouveau 'getMemberExpression.Body', avec les valeurs d'argument remplaçant chaque occurrence de paramètre. Retournez ensuite le corps de LambdaExpression. Comment cela vous semble-t-il? – smartcaveman

+0

@smartcaveman, j'ai finalement écrit une solution ExpressionVisitor, qui ne nécessite pas du tout Invoke. Voir ma mise à jour –

Questions connexes