Tag Archives: lambda

Les autres nouveautés de Visual Studio 2012

Visual Studio 2012 RC est sorti il y a 10 jours, et bien que je n’ai pas encore eu beaucoup de temps pour jouer avec, j’en suis assez satisfait pour l’instant. Beaucoup de choses ont déjà été dites sur le design, ainsi que sur les nouvelles fonctionnalités les plus importantes, mais il y a aussi beaucoup de petites améliorations moins remarquables qui vont nous faciliter la vie. Comme je n’ai pas vu grand chose d’écrit à ce sujet, je me suis dit qu’il serait utile de faire une petite liste de ce que j’ai remarqué jusqu’ici.

Amélioration de Modifier et Continuer: meilleure expérience de débogage avec les méthodes anonymes

Modifier et continuer est une fonctionnalité très utile qui est disponible dans Visual Studio depuis longtemps. C’est génial quand vous devez corriger du code en plein milieu d’une session de débogage pas-à-pas, sans avoir à redémarrer toute l’application. Cette fonctionnalité a toujours eu une limitation : elle n’était pas utilisable dans une méthode contenant une méthode anonyme ou une expression lambda, comme indiqué par ce message d’erreur :

Modifying a ’method’ which contains a lambda expression will prevent the debug session from continuing while Edit and Continue is enabled.

Avant .NET 3.5, ce n’était pas vraiment un problème car les méthodes anonymes n’étaient pas très courantes, mais depuis l’arrivée de Linq et la généralisation des expressions lambda, cette limitation était devenue de plus en plus gênante.

Eh bien, bonne nouvelle : Visual Studio 2012 règle ça ! Vous pouvez maintenant utiliser Modifier et Continuer dans une méthode qui contient des expressions lambda ou des requêtes Linq. Notez que la limitation n’a pas complètement disparu : il n’est toujours pas possible de modifier une instruction qui contient une méthode anonyme. Notez le message légèrement différent :

Modifying a statement which contains a lambda expression will prevent the debug session from continuing while Edit and Continue is enabled.

Mais vous pouvez modifier tout le reste du corps de la méthode, ce qui est une grosse amélioration. Notez que la modification d’une méthode qui contient des types anonymes est également supportée, tant que vous ne modifiez pas le type anonyme lui-même.

Optimisation de l’auto-complétion pour les gestionnaires d’évènements.

Visual Studio a une fonctionnalité d’auto-complétion très utile qui permet de générer un gestionnaire d’évènement automatiquement quand vous tapez += et Tab après un nom d’évènement :

image_thumb9

Mais la partie new EventHandler(…) est redondante, car depuis C# 2, un groupe de méthode est implicitement convertible en un delegate de type compatible. Visual Studio 2012 corrige cela et génère la forme courte :

image3_thumb

OK, c’est un tout petit changement, mais l’ancien comportement m’agaçait beaucoup… J’avais d’ailleurs suggéré cette amélioration sur Connect, donc je suis content de voir qu’elle a été implémentée.

Amélioration de Rechercher/Remplacer

Le dialogue de recherche de Visual Studio n’avait reçu aucune amélioration depuis une éternité (probablement depuis VS2003 ou VS2005), il était donc temps que ça change… Dans VS2012, il a été remplacé par la fonction Quick Find de l’extension Productivity Power Tools. Cela se présente sous forme d’un petit panneau dans le coin en haut à droite de l’éditeur, beaucoup plus discret que l’ancien dialogue :

image_thumb8

Ce panneau offre les fonctionnalités suivantes :

  • recherche incrémentale (les correspondances sont surlignées au fur et à mesure que vous tapez votre recherche)
  • accès rapide aux options telles que Respecter la casse, Mot entier ou Expression régulière, via la liste déroulante du champ de recherche
  • support des expressions régulières .NET (l’ancien dialogue de recherche utilisait un autre type de regex, pas totalement compatible avec la syntaxe des regex .NET)

Si, pour une raison ou une autre, vous voulez utiliser le dialogue de recherche complet, il est toujours là : un élément du menu déroulant du champ de recherche permet d’y accéder.

Lancement rapide

Où est cette commande déjà ? Dans le menu Déboguer ou dans le menu Projet ? Je n’arrive pas à m’en souvenir, et je ne connais pas le raccourci clavier…

Cette réflexion vous est familière ? A moi, oui… Visual Studio a tellement de fonctionnalités qu’il est parfois difficile de trouver ce que vous cherchez. C’est pourquoi la fonctionnalité de Lancement rapide est une nouveauté appréciable; elle se présente sous forme d’une boite de recherche dans le coin en haut à droite de l’EDI, et vous permet de simplement taper ce que vous voulez faire :

image_thumb7

Encore une fois, cette fonctionnalité vient de l’extension Productivity Power Tools, et a été incluse dans Visual Studio lui-même. Elle a aussi reçu une place plus en vue dans l’EDI (dans VS2010 elle était seulement accessible via le raccourci Ctrl+Q).

Un explorateur de solution plus malin

Jusqu’à maintenant, l’explorateur de solution affichait seulement les fichiers qui se trouvaient dans votre projet, mais rien sur le contenu des fichiers. Si vous vouliez voir les classes et membres, il fallait basculer sur la vue des classes. Visual Studio 2012 change cela : vous pouvez maintenant voir ce qui est déclaré dans le fichier, en dépliant simplement le nœud correspondant dans l’explorateur de solution :

image_thumb6

Cette fonctionnalité était présente dans Eclipse depuis longtemps, il était donc temps que Visual Studio rattrape ce retard Clignement d'œil.

Quelques autres fonctionnalités intéressantes du nouvel explorateur de solution :

  • Scope to This : permet de voir seulement l’arborescence à partir d’un nœud spécifique (par exemple si vous voulez voir seulement le contenu d’un dossier). La nouvelle arborescence peut être ouverte dans une vue séparée.
  • Pending changes filter : permet d’afficher uniquement les fichiers modifiés (si le contrôle de source est actif pour le projet)
  • Open files filter : permet de n’afficher que les fichiers actuellement ouverts
  • Sync With Active Document : permet de localiser rapidement le fichier courant dans l’explorateur de solution (très utile pour les grosses solutions)

Une fois de plus, c’est une fonctionnalité qui vient de Productivity Power Tools (elle s’appelait Solution Navigator), avec quelques améliorations.

MENUS EN LETTRES CAPITALES

Les titres des menus de VS2012 sont EN LETTRES CAPITALES, et ce n’est pas vraiment le changement le plus populaire… il y a eu une quantité impressionnante de retours négatifs à ce sujet! Franchement, je suis un peu surpris que les gens attachent autant d’importance à un détail si insignifiant, alors qu’il y a tant d’autres choses plus importantes à discuter. Personnellement, ça m’a un peu troublé quand j’ai vu les premières captures d’écran, mais quand j’ai pu mettre la main sur la RC, j’ai finalement trouvé que ça ne me gênait pas du tout. Je suppose que la plupart des gens finiront par s’y habituer…

Bref, la raison pour laquelle je mentionne ça, c’est simplement pour dire que si vraiment vous n’aimez pas les menus EN LETTRES CAPITALES, vous pouvez les remettre en casse normale avec une simple manipulation du registre. Ouvrez regedit, trouvez la clé HKEY_CURRENT_USER\Software\Microsoft\VisualStudio\11.0\General, créez une valeur DWORD nommée SuppressUppercaseConversion, et mettez sa valeur à 1.

[Entity Framework] Utiliser Include avec des expressions lambda

Je travaille en ce moment sur un projet qui utilise Entity Framework 4. Bien que le lazy loading soit activé, j’utilise généralement la méthode ObjectQuery.Include pour charger les entités associées en une seule fois, de façon à éviter des appels supplémentaires à la base de données lors de l’accès à ces entités :

var query =
    from ord in db.Orders.Include("OrderDetails")
    where ord.Date >= DateTime.Today
    select ord;

Ou encore, pour inclure aussi le produit :

var query =
    from ord in db.Orders.Include("OrderDetails.Product")
    where ord.Date >= DateTime.Today
    select ord;

Il y a quelque chose qui m’ennuie avec cette méthode Include : le fait de devoir spécifier le chemin de la propriété sous forme de chaine de caractères. En effet cette approche présente deux inconvénients majeurs :

  • Elle comporte un risque d’erreur important : on a vite fait de faire une faute de frappe dans le chemin de la propriété, et puisque c’est une chaine de caractères, le compilateur ne remarque rien. On a donc une erreur à l’exécution alors que ça aurait pu être vérifié dès la compilation.
  • On ne profite plus de l’assistance de l’IDE : pas d’intellisense ni de refactoring. Si on renomme une propriété du modèle, le refactoring automatique ne prend pas en compte le contenu des chaines de caractères. Il faut donc aller modifier manuellement les appels à Include qui font référence à cette propriété, avec le risque non négligeable d’en oublier au passage…

Il serait donc plus pratique d’utiliser une expression lambda pour spécifier le chemin de la propriété à inclure. Le principe est connu, et fréquemment utilisé pour éviter de passer une chaine quand on veut spécifier le nom d’une propriété.

Le cas “de base”, dans lequel on ne charge qu’une propriété directement liée à la source, est assez simple à gérer, et on trouve des implémentations un peu partout sur le net. Il suffit d’utiliser une méthode qui extrait le nom de la propriété à partir de l’expression :

    public static class ObjectQueryExtensions
    {
        public static ObjectQuery<T> Include<T>(this ObjectQuery<T> query, Expression<Func<T, object>> selector)
        {
            string propertyName = GetPropertyName(selector);
            return query.Include(propertyName);
        }

        private static string GetPropertyName<T>(Expression<Func<T, object>> expression)
        {
            MemberExpression memberExpr = expression.Body as MemberExpression;
            if (memberExpr == null)
                throw new ArgumentException("Expression body must be a member expression");
            return memberExpr.Member.Name;
        }
    }

En utilisant cette méthode d’extension, on peut réécrire le code du premier exemple de la façon suivante :

var query =
    from ord in db.Orders.Include(o => o.OrderDetails)
    where ord.Date >= DateTime.Today
    select ord;

Ce code fonctionne, mais seulement pour les cas simples… dans le deuxième exemple, on veut aussi inclure la propriété OrderDetail.Product, et le code ci-dessus ne permet pas de gérer ce cas. En effet, l’expression qu’il faudrait écrire pour inclure la propriété Product serait du type o.OrderDetails.Select(od => od.Product), or la méthode GetPropertyName ne sait gérer que les propriétés, pas les appels de méthode…

Pour obtenir le chemin complet de la propriété à inclure, il faut parcourir tout l’arbre d’expression pour en extraire les propriétés. Bien que cela puisse paraitre assez complexe, il existe une classe qui peut nous y aider : ExpressionVisitor. Cette classe, introduite en .NET 4.0, implémente le design pattern Visiteur pour parcourir tous les noeuds de l’arbre. L’implémentation de base ne fait rien de particulier, elle se contente de visiter chaque noeud. Tout ce que nous avons à faire, c’est en hériter pour spécialiser certaines méthodes de façon à extraire les propriétés utilisées dans l’expression. On va donc redéfinir les méthodes suivantes :

  • VisitMember : c’est la méthode appelée pour visiter l’accès à une propriété ou à un champ
  • VisitMethodCall : la méthode appelée pour visiter les appels de méthode. Bien que ce cas ne nous intéresse pas directement a priori, on doit modifier son comportement dans le cas des opérateurs Linq : l’implémentation par défaut visite les paramètres dans l’ordre normal, mais pour les méthodes d’extension comme Select ou SelectMany, on doit visiter le premier paramètre (le paramètre this) en dernier, de façon à conserver l’ordre voulu pour le chemin de la propriété

Voici donc la nouvelle implémentation de la méthode d’extension Include :

    public static class ObjectQueryExtensions
    {
        public static ObjectQuery<T> Include<T>(this ObjectQuery<T> query, Expression<Func<T, object>> selector)
        {
            string path = new PropertyPathVisitor().GetPropertyPath(expression);
            return query.Include(path);
        }

        class PropertyPathVisitor : ExpressionVisitor
        {
            private Stack<string> _stack;

            public string GetPropertyPath(Expression expression)
            {
                _stack = new Stack<string>();
                Visit(expression);
                return _stack
                    .Aggregate(
                        new StringBuilder(),
                        (sb, name) =>
                            (sb.Length > 0 ? sb.Append(".") : sb).Append(name))
                    .ToString();
            }

            protected override Expression VisitMember(MemberExpression expression)
            {
                if (_stack != null)
                    _stack.Push(expression.Member.Name);
                return base.VisitMember(expression);
            }

            protected override Expression VisitMethodCall(MethodCallExpression expression)
            {
                if (IsLinqOperator(expression.Method))
                {
                    for (int i = 1; i < expression.Arguments.Count; i++)
                    {
                        Visit(expression.Arguments[i]);
                    }
                    Visit(expression.Arguments[0]);
                    return expression;
                }
                return base.VisitMethodCall(expression);
            }

            private static bool IsLinqOperator(MethodInfo method)
            {
                if (method.DeclaringType != typeof(Queryable) && method.DeclaringType != typeof(Enumerable))
                    return false;
                return Attribute.GetCustomAttribute(method, typeof(ExtensionAttribute)) != null;
            }
        }
    }

J’ai déjà parlé plus haut de la méthode VisitMethodCall, je ne reviens donc pas dessus. L’implémentation de VisitMember est très simple : on se contente d’ajouter le nom de la propriété sur une pile. Au fait, pourquoi une pile ? Parce que la visite de l’expression ne se déroule pas dans l’ordre auquel on pense intuitivement. Par exemple, dans une expression du type o.OrderDetails.Select(od => od.Product), le premier noeud examiné n’est pas o, mais l’appel à Select, car ce qui précède (o.OrderDetails) est en fait un paramètre de la méthode statique Select… Pour obtenir les propriétés dans l’ordre voulu, on les place donc sur une pile de façon à les relire ensuite dans l’ordre inverse.

La méthode GetPropertyPath est elle aussi assez facile à comprendre : elle initialise la pile, visite l’expression, et reconstitue le chemin à partir de la pile.

On peut donc maintenant réécrire le code du deuxième exemple de la façon suivante :

var query =
    from ord in db.Orders.Include(o => OrderDetails.Select(od => od.Product))
    where ord.Date >= DateTime.Today
    select ord;

Cette méthode fonctionne aussi pour des cas plus complexes. Ajoutons un peu de piment à notre exemple : une ou plusieurs remises peuvent être appliquées à chaque article commandé, et chaque remise est associée à une campagne promotionnelle. Si on veut inclure les remises et les campagnes associées dans les résultats de la requête, on peut écrire quelque chose comme ça :

var query =
    from ord in db.Orders.Include(o => OrderDetails.Select(od => od.Discounts.Select(d => d.Campaign)))
    where ord.Date >= DateTime.Today
    select ord;

Le résultat est le même que si on avait passé à Include le chemin “OrderDetails.Discounts.Campaign”. Comme les Select imbriqués réduisent la lisibilité du code, on peut écrire l’expression un peu différemment :

var query =
    from ord in db.Orders.Include(o => o.OrderDetails
                                        .SelectMany(od => od.Discounts)
                                        .Select(d => d.Campaign))
    where ord.Date >= DateTime.Today
    select ord;

Pour finir, deux remarques sur cette solution :

  • Une méthode d’extension similaire est inclue dans le Entity Framework Feature CTP4 (voir cet article pour plus de détails). Il est donc probable qu’elle finisse par être intégrée dans le Framework (peut-être dans un service pack pour .NET 4.0 ?)
  • Bien que cette solution cible Entity Framework 4.0, il est a priori possible de l’adapter à EF 3.5. La classe ExpressionVisitor n’est pas disponible en 3.5, mais le LINQKit de Joseph Albahari en fournit une implémentation. Je n’ai pas essayé, mais ça devrait fonctionner de la même façon…