Combinez les expressions Lambda

asp.net-mvc-3 c# expression-trees

Question

Je cherche un moyen de combiner deux expressions lambda, sans utiliser Expression.Invoke sur l'une ou l'autre expression. Je veux essentiellement construire une nouvelle expression qui enchaîne deux différentes. Considérons le code suivant:

class Model {
    public SubModel SubModel { get; set;}
}

class SubModel {
    public Foo Foo { get; set; }
}

class Foo {
    public Bar Bar { get; set; }
}

class Bar {
    public string Value { get; set; }
}

Et disons que j'avais deux expressions:

class Model {
    public SubModel SubModel { get; set;}
}

class SubModel {
    public Foo Foo { get; set; }
}

class Foo {
    public Bar Bar { get; set; }
}

class Bar {
    public string Value { get; set; }
}

Et je souhaite les associer pour obtenir fonctionnellement l'expression suivante:

class Model {
    public SubModel SubModel { get; set;}
}

class SubModel {
    public Foo Foo { get; set; }
}

class Foo {
    public Bar Bar { get; set; }
}

class Bar {
    public string Value { get; set; }
}

La seule façon pour moi d’y arriver est d’utiliser un ExpressionVisitor comme celui-ci:

class Model {
    public SubModel SubModel { get; set;}
}

class SubModel {
    public Foo Foo { get; set; }
}

class Foo {
    public Bar Bar { get; set; }
}

class Bar {
    public string Value { get; set; }
}

Et puis son utilisé comme ceci:

class Model {
    public SubModel SubModel { get; set;}
}

class SubModel {
    public Foo Foo { get; set; }
}

class Foo {
    public Bar Bar { get; set; }
}

class Bar {
    public string Value { get; set; }
}

Cela fonctionne, mais cela me semble un peu maladroit. J'essaie encore d'envelopper mes expressions de tête et de me demander s'il existe un moyen plus idiomatique d'exprimer cela, et j'ai le soupçon sournois que je manque quelque chose d'évident.


La raison pour laquelle je veux faire cela est de l'utiliser avec les assistants ASP.net mvc 3 Html. J'ai des ViewModels profondément imbriqués et des extensions HtmlHelper qui aident à les gérer. L'expression doit donc être simplement une collection de MemberExpressions pour que les aides MVC intégrées puissent les traiter correctement et créer les valeurs d'attribut de nom correctement imbriquées. Mon premier instinct a été d'utiliser Expression.Invoke() et d'appeler la première expression et de l'enchaîner à la seconde, mais les assistants de MVC ne l'aimaient pas beaucoup. Il a perdu son contexte hiérarchique.

Réponse acceptée

Utilisez un visiteur pour permuter toutes les occurrences du paramètre f en m.SubModel.Foo et créez une nouvelle expression avec m comme paramètre:

internal static class Program
{
    static void Main()
    {

        Expression<Func<Model, Foo>> expression1 = m => m.SubModel.Foo;
        Expression<Func<Foo, string>> expression2 = f => f.Bar.Value;

        var swap = new SwapVisitor(expression2.Parameters[0], expression1.Body);
        var lambda = Expression.Lambda<Func<Model, string>>(
               swap.Visit(expression2.Body), expression1.Parameters);

        // test it worked
        var func = lambda.Compile();
        Model test = new Model {SubModel = new SubModel {Foo = new Foo {
             Bar = new Bar { Value = "abc"}}}};
        Console.WriteLine(func(test)); // "abc"
    }
}
class SwapVisitor : ExpressionVisitor
{
    private readonly Expression from, to;
    public SwapVisitor(Expression from, Expression to)
    {
        this.from = from;
        this.to = to;
    }
    public override Expression Visit(Expression node)
    {
         return node == from ? to : base.Visit(node);
    }
}

Réponse populaire

Votre solution semble être étroitement adaptée à votre problème spécifique, ce qui semble inflexible.

Il me semble que vous pourriez résoudre votre problème assez simplement par une simple substitution lambda: remplacez les occurrences du paramètre (ou "variable libre" comme elles l'appellent dans le calcul lambda) par le corps. (Voir la réponse de Marc pour un code à faire.)

Comme les paramètres des arbres d'expression ont une identité référentielle plutôt qu'une identité de valeur, il n'est même pas nécessaire de les renommer en alpha.

C'est-à-dire que vous avez:

Expression<Func<A, B>> ab = a => f(a);  // could be *any* expression using a
Expression<Func<B, C>> bc = b => g(b);  // could be *any* expression using b

et vous souhaitez produire la composition

Expression<Func<A, B>> ab = a => f(a);  // could be *any* expression using a
Expression<Func<B, C>> bc = b => g(b);  // could be *any* expression using b

Prenez donc le corps g(b) , effectuez une recherche et remplacez le visiteur à la recherche de ParameterExpression pour b et remplacez-le par le corps f(a) pour vous donner le nouveau corps g(f(a)) . Créez ensuite un nouveau lambda avec le paramètre a qui a ce corps.




Sous licence: CC-BY-SA with attribution
Non affilié à Stack Overflow
Est-ce KB légal? Oui, apprenez pourquoi
Sous licence: CC-BY-SA with attribution
Non affilié à Stack Overflow
Est-ce KB légal? Oui, apprenez pourquoi