Blog Arolla

Linq provider : un essai… partie 4

Post précédent de la série.

Utilisons le pattern Visiteur pour manipuler des arbres d’expression.

Dans les billets de cette série, j’ai commencé à décrire une implémentation partielle d’un provider Linq. L’objectif de ce provider est de récupérer des données depuis un web service, et pour réussir à renvoyer ces données à l’appelant du provider, des manipulations d’arbres d’expression sont nécessaires. Après que nous ayons obtenu les données depuis le service, nous voulons injecter dans l’arbre d’expression représentant la requête, une référence vers ces données récupérées, à la place de l’expression représentant l’instance de QueryableDummyData<Person>, qui au final n’est qu’un marqueur de position, un “placeholder” au sein de cet arbre.

Afin de remplacer ce QueryableDummyData<Person> par un tableau de Person[], nous utilisons une classe nommée ExpressionTreeConstantReplacer, qui dérive de la classe ExpressionVisitor, intégrée nativement danse le Framework .NET 4.0, et qui elle-même est basée sur le pattern Visiteur.

Appliqué aux arbres d’expression, le pattern Visiteur peut être utilisé pour générer un nouvel arbre d’expression à partir de l’arbre visité, pour accumuler de la donnée au cours de la visite de l’arbre, ou encore pour faire les deux en même temps. Chaque surcharge méthode “Visit” retourne une expression, qui est par défaut l’expression non modifiée qu’elle vient juste de visiter. Une méthode surchargée peut renvoyer un nouvel arbre, mais ne doit pas modifier l’expression visitée. Cette approche a l’avantage de ne pas avoir d’effet de bord sur l’arbre d’expression visité.

Pour exemple, le pattern Visiteur peut être utilisé pour transformer l’arbre suivant (cliquez pour accéder à l’exemple HTML) :

Dans ce nouvel arbre, qui est identique à ceci près que la clause where p => p.Age >= 21 a été supprimé :

La classe ExpressionVisitor est conçue pour être utilisée comme classe de base à des classes de manipulation d’arbres. Lorsque la méthode Visit de la classe ExpressionVisitor est appelée, elle détermine le type du nœud passé en paramètre, et visite récursivement le nœud et tous ses enfants, en utilisant à chaque fois la méthode adaptée au type du nœud concerné.

Cela permet au développeur de la classe dérivée de surcharger uniquement les méthodes nécessaires. Par exemple, puisque nous voulons ici remplacer une noeud de type “Constante” par un autre, la classe ExpressionTreeConstantReplacer dérive de ExpressionVisitor et surcharge la méthode VisitConstant.

internal class ExpressionTreeConstantReplacer<TReplacement>
    : ExpressionVisitor
{
    private Type originalType;
    private TReplacement replacementConstant;

    internal ExpressionTreeConstantReplacer(
        Type originalType, TReplacement replacementConstant)
    {
        this.originalType = originalType;
        this.replacementConstant = replacementConstant;
    }

    protected override Expression VisitConstant(
        ConstantExpression c)
    {
        if (c.Type == this.originalType)
            return Expression.Constant(this.replacementConstant);
        else
            return c;
    }
}

Le constructeur de cette classe prend deux arguments : le type de la constante d’origine à remplacer, et la nouvelle valeur à injecter. Par la suite, durant le processus de visite de l’arbre, lorsqu’un nœud de type “Constante” est détecté, et si son type correspond au type à remplacer, alors un nouveau nœud de type “Constante”, contenant la valeur de remplacement, est retourné à la place du nœud de départ.

Afin de rendre la syntaxe des appels de remplacement plus expressive, une classe utilitaire a aussi été introduite, pour tirer parti de l’inférence de type de C#, qui est disponible sur les appels de méthodes statiques (et donc d’extension), mais pas sur les constructeurs :

internal class ExpressionTreeConstantReplacer
{
    internal static Expression CopyAndReplace<TReplacement>(
        Expression expression,
        Type originalType,
        TReplacement replacementConstant)
    {
        var modifier =
            new ExpressionTreeConstantReplacer<TReplacement>(
                originalType,
                replacementConstant);
        return modifier.Visit(expression);
    }
}

Cela nous permet d’écrire désormais le remplacement de la manière suivante :

Expression finalExpressionTree =
    ExpressionTreeConstantReplacer
        .CopyAndReplace(
            filteredExpressionTree,
            typeof(QueryableDummyData<Person>),
            queryablePersons);

Dans le prochain billet, je décrirai la classe TreeVisualizer que j’ai écrite afin de générer les visualisations d’arbres d’expression présentes dans ce post.

Site Web | Plus de publications

Contributeur enthousiaste

2 comments for “Linq provider : un essai… partie 4