Category Archives: Uncategorized

StringTemplate: une autre approche de l’interpolation de chaines

Avec la version 6 de C# qui approche, il y a beaucoup de discussions sur CodePlex et ailleurs à propos de l’interpolation de chaines. Pas très étonnant, puisqu’il s’agit d’une des fonctionnalités majeures de cette version… Au cas où vous auriez vécu dans une grotte ces derniers mois et n’en auriez pas entendu parler, l’interpolation de chaines est un moyen d’insérer des expressions C# à l’intérieur d’une chaine de caractère, de façon à ce qu’elles soient évaluées lors de l’exécution et remplacées par leurs valeurs. En gros, vous écrivez quelque chose comme ça :

string text = $"{p.Name} est né le {p.DateOfBirth:D}";

Et le compilateur le transforme en ceci :

string text = String.Format("{0} est né le {1:D}", p.Name, p.DateOfBirth);

Note: la syntaxe présentée ci-dessus correspond aux dernières notes de conception sur cette fonctionnalité. Elle peut encore changer d’ici à la sortie finale, et la preview actuelle de VS2015 utilise encore une syntaxe différente : “\{p.Name} est né le \{p.DateOfBirth:D}”.

J’adore cette fonctionnalité. Elle va être extrêmement pratique pour des choses comme le logging, la génération d’URL ou de requêtes, etc. Je l’utiliserai certainement beaucoup, surtout maintenant que Microsoft a écouté les retours de la communauté et a inclus un moyen de personnaliser la façon dont les expressions dans la chaine sont évaluées (regardez la partie à propos de IFormattable dans les notes de conception).

Cependant, quelque chose me chiffonne : puisque les chaines interpolées sont interprétées par le compilateur, elles doivent être codées en dur ; on ne peut pas les extraire dans des ressources pour la localisation. Cela signifie que cette fonctionnalité n’est pas utilisable pour la localisation, et qu’on est obligés de continuer à utiliser des marqueurs numériques dans les chaines localisées.

Mais est-ce vraiment inévitable ?

Depuis quelques années, j’utilise un moteur d’interpolation de chaines que j’ai créé, qui s’utilise de la même façon que String.Format, mais avec des marqueurs nommés plutôt que des numéros. Il prend une chaine de format, et un objet avec des propriétés qui correspondent aux noms des marqueurs :

string text = StringTemplate.Format("{Name} est né le {DateOfBirth:D}", new { p.Name, p.DateOfBirth });

Bien sûr, si vous avez déjà un objet avec les propriétés que vous voulez inclure dans la chaine, vous pouvez simplement passer cet objet directement :

string text = StringTemplate.Format("{Name} est né le {DateOfBirth:D}", p);

Le résultat est exactement ce à quoi on pourrait s’attendre : les marqueurs sont remplacés par les valeurs des propriétés correspondantes.

En quoi est-ce mieux que String.Format ?

  • C’est beaucoup plus lisible; un marqueur nommé indique immédiatement quelle valeur ira à cet emplacement
  • On est moins susceptible de se tromper : pas besoin de faire attention à l’ordre des valeurs à formater
  • Quand on extrait les chaines de format dans des ressources pour la localisation, le traducteur voit un nom dans le marqueur, pas un numéro. Cela donne plus de contexte à la chaine, et permet de comprendre plus facilement à quoi la chaine finale va ressembler.

Notez que vous pouvez utiliser les mêmes spécificateurs de format que dans String.Format. La classe StringTemplate analyse la chaine de format et la transforme en une chaine compatible avec String.Format, extrait les valeurs des propriétés dans un tableau, et appelle String.Format.

Bien sûr, analyser la chaine et extraire les valeurs des propriétés par réflexion à chaque fois serait très inefficace, il y a donc des optimisations :

  • chaque chaine de format distincte est analysée une seule fois, et le résultat de l’analyse est mis en cache pour être réutilisé plus tard.
  • pour chaque propriété utilisée dans une chaine de format, un delegate accesseur est généré et mis en cache, pour éviter de faire appel à la réflexion à chaque fois.

Cela signifie que la première fois que vous utilisez une chaine de format données, il y a un coût lié à l’analyse et à la génération des delegates, mais les utilisations ultérieures de la même chaine de format sont beaucoup plus rapides.

La classe StringTemplate fait partie d’une librairie nommée NString, qui contient également des méthodes d’extension pour faciliter la manipulation de chaines. La librairie est une PCL qui peut être utilisée avec toutes les variantes de .NET à l’exception de Silverlight 5. Un paquet NuGet est disponible ici.

Un moyen facile de tester unitairement la validation des arguments null

Quand on teste unitairement une méthode, une des choses à tester est la validation des arguments : par exemple, vérifier que la méthode lève bien une ArgumentNullException quand un argument null est passé pour un paramètre qui ne doit pas être null. Ecrire ce genre de test est très facile, mais c’est une tâche fastidieuse et répétitive, surtout pour une méthode qui a beaucoup de paramètres. J’ai donc écrit une méthode qui automatise en partie cette tâche : elle essaie de passer null pour chacun des arguments spécifiés, et vérifie que la méthode lève bien une ArgumentNullException. Voici un exemple qui teste une méthode d’extension FullOuterJoin :

[Test]
public void FullOuterJoin_Throws_If_Argument_Null()
{
    var left = Enumerable.Empty<int>();
    var right = Enumerable.Empty<int>();
    TestHelper.AssertThrowsWhenArgumentNull(
        () => left.FullOuterJoin(right, x => x, y => y, (k, x, y) => 0, 0, 0, null),
        "left", "right", "leftKeySelector", "rightKeySelector", "resultSelector");
}

Le premier paramètre est une expression lambda qui représente la façon d’appeler la méthode testée. Dans cette lambda, tous les arguments passés à la méthode doivent être valides. Les paramètres suivants sont les noms des paramètres qui ne doivent pas être null. Pour chacun des noms spécifiés, AssertThrowsWhenArgumentNull va remplacer l’argument correspondant par null dans l’expression lambda, compiler et invoquer l’expression lambda, et vérifier que la méthode lève bien une ArgumentNullException.

Grâce à cette méthode, au lieu d’écrire un test pour chacun des arguments à valider, il suffit d’un seul test.

Voici le code de la méthode TestHelper.AssertThrowsWhenArgumentNull (vous pouvez aussi le trouver sur Gist):

using System;
using System.Linq;
using System.Linq.Expressions;
using NUnit.Framework;

namespace MyLibrary.Tests
{
    static class TestHelper
    {
        public static void AssertThrowsWhenArgumentNull(Expression<TestDelegate> expr, params string[] paramNames)
        {
            var realCall = expr.Body as MethodCallExpression;
            if (realCall == null)
                throw new ArgumentException("Expression body is not a method call", "expr");

            var realArgs = realCall.Arguments;
            var paramIndexes = realCall.Method.GetParameters()
                .Select((p, i) => new { p, i })
                .ToDictionary(x => x.p.Name, x => x.i);
            var paramTypes = realCall.Method.GetParameters()
                .ToDictionary(p => p.Name, p => p.ParameterType);
            
            

            foreach (var paramName in paramNames)
            {
                var args = realArgs.ToArray();
                args[paramIndexes[paramName]] = Expression.Constant(null, paramTypes[paramName]);
                var call = Expression.Call(realCall.Method, args);
                var lambda = Expression.Lambda<TestDelegate>(call);
                var action = lambda.Compile();
                var ex = Assert.Throws<ArgumentNullException>(action, "Expected ArgumentNullException for parameter '{0}', but none was thrown.", paramName);
                Assert.AreEqual(paramName, ex.ParamName);
            }
        }

    }
}

Notez que cette méthode a été écrite pour NUnit, mais vous pouvez facilement l’adapter à d’autres frameworks de test unitaire.

J’ai utilisé cette méthode dans ma librairie Linq.Extras, qui fournit de nombreuses méthodes d’extension supplémentaires pour travailler avec des séquences et collections (elle inclut par exemple la méthode FullOuterJoin mentionnée plus haut).

Envoyer des données avec HttpClient selon un modèle “push”

Si vous avez déjà utilisé la classe HttpWebRequest pour envoyer des données, vous savez qu’elle utilise un modèle “push”. Ce que j’entends par là, c’est que vous appelez la méthode GetRequestStream, qui ouvre la connexion si nécessaire, envoie les en-têtes, et renvoie un flux sur lequel vous pouvez écrire directement.

.NET 4.5 a introduit la classe HttpClient comme nouveau moyen de communiquer en HTTP. Elle repose en fait sur HttpWebRequest en interne, mais offre une API plus pratique et complètement asynchrone. HttpClient utilise une approche différente en ce qui concerne l’upload de données : au lieu d’écrire directement sur le flux de la requête, il faut affecter à la propriété Content du HttpRequestMessage une instance d’une classe dérivée de HttpContent. On peut également passer le contenu directement aux méthodes PostAsync ou PutAsync.

Le framework .NET fournit quelques implémentations standard de HttpContent, voici quelques unes des plus communément utilisées :

  • ByteArrayContent: représente du contenu binaire brut en mémoire
  • StringContent: représente du texte avec un encodage spécifique (c’est une spécialisation de ByteArrayContent)
  • StreamContent: représente des données binaires brutes sous forme d’un flux

Par exemple, voilà comment on peut uploader le contenu d’un fichier :

async Task UploadFileAsync(Uri uri, string filename)
{
    using (var stream = File.OpenRead(filename))
    {
        var client = new HttpClient();
        var response = await client.PostAsync(uri, new StreamContent(stream));
        response.EnsureSuccessStatusCode();
    }
}

Comme vous l’aurez peut-être remarqué, ce code n’écrit jamais explicitement sur le flux de la requête : le contenu est automatiquement lu depuis le flux source et copié vers le flux de la requête.

Ce modèle “pull” convient à la plupart des usages, mais il a un inconvénient : il nécessite que les données à envoyer existent déjà sous une forme qui peut être directement envoyée au serveur. Ce n’est pas toujours souhaitable, parce que parfois on veut générer “à la volée” le contenu de la requête. Par exemple, si on veut envoyer un objet sérialisé en JSON, avec l’approche “pull”, il faut d’abord le sérialiser en mémoire dans une String ou un MemoryStream, puis affecter cela au contenu de la requête :

async Task UploadJsonObjectAsync<T>(Uri uri, T data)
{
    var client = new HttpClient();
    string json = JsonConvert.SerializeObject(data);
    var response = await client.PostAsync(uri, new StringContent(json));
    response.EnsureSuccessStatusCode();
}

Ce n’est pas vraiment un problème pour des petits objets, mais ce n’est clairement pas optimal pour des graphes d’objets plus importants…

Alors, comment pourrait-on inverser ce modèle pull en un modèle push ? Eh bien c’est en fait assez simple : il suffit de créer une classe qui hérite de HttpContent, et de redéfinir sa méthode SerializeToStreamAsync pour écrire directement sur le flux de la requête. En fait, j’avais l’intention de bloguer sur ma propre implémentation, mais j’ai fait quelques recherches, et il s’avère que Microsoft a déjà fait le travail : la librairie Web API 2 Client fournit une classe PushStreamContent qui fait exactement ça. En gros, il faut juste lui passer un délégué qui définit quoi faire avec le flux de la requête. Voilà comment ça fonctionne :

async Task UploadJsonObjectAsync<T>(Uri uri, T data)
{
    var client = new HttpClient();
    var content = new PushStreamContent((stream, httpContent, transportContext) =>
    {
        var serializer = new JsonSerializer();
        using (var writer = new StreamWriter(stream))
        {
            serializer.Serialize(writer, data);
        }
    });
    var response = await client.PostAsync(uri, content);
    response.EnsureSuccessStatusCode();
}

Notez que la classe PushStreamContent fournit aussi une surcharge du constructeur qui accepte un délégué asynchrone, si vous voulez écrire sur le flux de façon asynchrone.

En fait, pour ce cas d’utilisation spécifique, la librairie Web API 2 Client propose une approche moins alambiquée : la classe ObjectContent. Il faut juste lui passer l’objet à envoyer ainsi qu’un MediaTypeFormatter, et elle se charge de sérialiser l’objet sur le flux de la requête :

async Task UploadJsonObjectAsync<T>(Uri uri, T data)
{
    var client = new HttpClient();
    var content = new ObjectContent<T>(data, new JsonMediaTypeFormatter());
    var response = await client.PostAsync(uri, content);
    response.EnsureSuccessStatusCode();
}

Par défaut, la classe JsonMediaTypeFormatter utilise Json.NET comme sérialiseur JSON, mais il y a une option pour utiliser DataContractJsonSerializer à la place.

Notez que si vous voulez lire un objet depuis le contenu de la réponse, c’est encore plus facile : utilisez simplement la méthode d’extension ReadAsAsync<T> (également dans la librairie Web API 2 Client). Comme vous pouvez le voir, il est donc extrêmement facile de consommer des APIs REST à l’aide de HttpClient.

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.