Tirer parti des annotations de ReSharper

Très mauvaisMauvaisMoyenBonExcellent (1 votes) 
Loading...Loading...

Je ne pense pas qu’il soit vraiment nécessaire de présenter ReSharper (souvent abbrégé R#), mais au cas où vous ne connaitriez pas, il s’agit d’un outil créé par JetBrains qui analyse votre code C# ou VB.NET en temps réel pour vous avertir des bugs potentiels, mauvaises pratiques, non-respect des conventions, etc. Il propose aussi de nombreux refactorings et générateurs de code bien utiles. Je l’utilise depuis quelques années maintenant, et cela a beaucoup amélioré ma productivité et la qualité de mon code.

Entre autres choses, R# vous avertit sur l’utilisation incorrecte de méthodes du .NET Framework. Par exemple, si vous appelez Path.GetFullPath avec un chemin qui est peut-être null, il affiche un avertissement :

image

Comment R# sait-il que Path.GetFullPath n’accepte pas d’argument null ? Et d’ailleurs, comment sait-il que Console.ReadLine peut renvoyer null ? Je suppose que ça aurait pu être codé en dur dans l’outil, mais ce ce ne serait pas une approche très élégante, et ne permettrait pas l’extensibilité… En fait, ReSharper utilise des annotations externes. Ce sont des fichiers XML qui sont fournis avec R# et contiennent des métadonnées à propos des classes et méthodes du .NET Framework. Ces données sont utilisées par l’analyseur pour détecter les problèmes potentiels dans votre code.

Soit, mais alors qu’en est-il des bibliothèques tierces ? Evidemment, JetBrains ne peut pas créer les annotations pour celles-ci, il y en a trop. La bonne nouvelle, c’est que vous pouvez écrire vos propres fichiers d’annotations externes pour les bibliothèques que vous utilisez, ou pour votre propre code. Cependant, pour votre code, il y a un moyen beaucoup plus pratique : vous pouvez appliquer les annotations directement dans votre code à l’aide d’attributs. Il y a deux moyens d’obtenir ces attributs :

  • Référencer l’assembly où ils sont définis (JetBrains.Annotations.dll dans le répertoire d’installation de R#). C’est très bien si ça ne vous gêne pas d’avoir une référence vers quelque chose qui n’a rien à voir avec votre application, mais ce n’est probablement pas une bonne idée pour une librairie.
  • Déclarer les attributs dans votre propre code. En fait, il n’est pas nécessaire de les écrire soi-même, car R# a une option pour copier leur implémentation dans le presse-papier, comme illustré ci-dessous. Il suffit ensuite de les coller dans un fichier de code du projet.

image

Maintenant qu’on a les attributs, comment les utilise-t-on ? Je vais présenter quelques unes des annotations les plus fréquemment utilisées.

NotNull

Cette annotation indique que l’élément sur lequel il est appliqué ne peut pas, ou ne doit pas, être null.

Si elle est appliquée sur une méthode ou propriété, cela signifie que celle-ci ne renverra jamais null:

[NotNull]
public string GetString()
{
    return "Hello world!";
}

Quand une méthode a cet attribut, si vous testez que la valeur de retour est null (ou non null), R# vous avertira que la condition est toujours fausse (ou vraie) :

image

 

Si l’annotation est appliquée à un paramètre de méthode, cela signifie que null n’est pas une valeur d’argument valide :

public string Repeat([NotNull] string s)
{
    if (s == null)
        throw new ArgumentNullException("s");
    return s + s;
}

Si R# détermine que la valeur passée pour s peut être null, il vous en avertira, comme illustré dans le premier exemple de cet article.

Cette annotation peut être ajoutée automatiquement en utilisant le menu  “quick fix” de ReSharper. L’option “Not null” va juste ajouter l’annotation; l’option “Check parameter for null” va ajouter l’annotation ainsi qu’une vérification de la valeur:

image

image

 

CanBeNull

C’est le contraire de NotNull. Appliquée à une méthode ou propriété, cette annotation signifie que cette méthode ou propriété peut renvoyer une valeur null. Appliquée à un paramètre, elle signifie que null est une valeur d’argument valide.

Pure

Celle-ci est très utile. Appliquée à une méthode, elle signifie que la méthode est pure. Une méthode pure n’a pas d’effet de bord observable, donc si vous n’utilisez pas la valeur renvoyée par la méthode, l’appel n’a aucun effet utile, c’est donc probablement une erreur. Exemple typique avec String.Replace:

image

 

StringFormatMethod

Cette annotation indique qu’une méthode fonctionne sur le même principe que la méthode String.Format, c’est-à-dire qu’elle prend une chaine de format composite, suivie d’un nombre variable d’arguments qui remplaceront les “trous” dans la chaine de format.

[StringFormatMethod("format")]
public static void Log(string format, params object[] args)
{
    ...
}

Cela permet à R# de vous avertir si les trous et les arguments ne correspondent pas :

image

UsedImplicitly

Celle-ci indique à ReSharper qu’un élément de code est utilisé, même si R# ne peut pas le détecter statiquement. Cela a pour effet d’inhiber l’avertissement qui dit qu’un élément n’est pas utilisé. Cela est utile, par exemple, quand un type ou membre est utilisé uniquement via la réflexion.

NoEnumeration

Cette annotation est appliquée à un paramètre IEnumerable, et signifie que la méthode n’énumèrera pas la séquence. R# donne un avertissement quand un IEnumerable est énuméré plusieurs fois, donc utiliser cet attribut permet d’éviter les faux positifs pour cet avertissement :

public static IEnumerable<T> EmptyIfNull<T>([NoEnumeration] this IEnumerable<T> source)
{
    return source ?? Enumerable.Empty<T>();
}

InstantHandle

Celle-ci s’applique à un paramètre de type delegate, et signifie que le delegate sera exécuté uniquement pendant l’exécution de la méthode. Cela permet d’empêcher l’avertissement “Access to modified closure”, qui apparait quand une expression lambda capture une variable qui est modifiée ultérieurement.

ContractAnnotation

Cette annotation est un moyen puissant de décrire les dépendances entre les entrées et la sortie d’une méthode. Cela permet à R# de prédire la façon dont une méthode se comportera. Par exemple, cette méthode renverra null si son argument est null, et non null sinon :

[ContractAnnotation("null => null; notnull => notnull")]
public object Transform(object data)
{
    ...
}

Grâce à cette annotation, ReSharper saura que si l’argument était non null, le résultat ne sera pas null non plus.

La méthode suivante ne se termine pas normalement (elle lance une exception) si son argument est null:

[ContractAnnotation("value:null => halt")]
public static void CheckArgumentNull<T>(
    [NoEnumeration] this T value,
    [InvokerParameterName] string paramName)
    where T : class
{
    if (value == null)
        throw new ArgumentNullException(paramName);
}

Cela permet à R# de savoir que si vous passez null à cette méthode, le code qui suit l’appel ne sera jamais atteint; s’il est atteint, on peut supposer que la valeur n’est pas null.

LocalizationRequired

Cette annotation signifie qu’une propriété ou un paramètre de méthode devrait être localisé; si vous passez une valeur codée en dur, R# vous avertira et suggèrera de l’extraire vers un fichier de ressource.

image11

Conclusion

Maintenant, vous vous demandez peut-être “mais pourquoi est-ce que je me donnerais tant de mal pour ajouter toutes ces annotations à mon code?”. La raison est simple : cela aide ReSharper à vous aider ! En lui donnant plus d’informations sur votre code, vous permettez à R# de vous donner des recommandations plus pertinentes et de produire moins de faux positifs. De plus, si vous être un auteur de bibliothèque, cela améliore l’utilisation de votre bibliothèque pour les utilisateurs de ReSharper. J’utilise beaucoup les annotations R# dans ma bibliothèque Linq.Extras, c’est donc un bon endroit pour trouver d’autres exemples.

Remarquez que je n’ai décrit qu’une petite partie des annotations disponibles. Il y en a beaucoup d’autres, principalement destinées à des scénarios spécifiques à ASP.NET. Vous pouvez les voir dans le fichier d’annotations généré par ReSharper, ou dans la documentation (qui n’est pas tout à fait complète, mais quand même utile).

Casse-tête C# n°1

Très mauvaisMauvaisMoyenBonExcellent (Pas encore de note) 
Loading...Loading...

J’adore résoudre des casse-têtes en C#; je pense que c’est un excellent moyen d’approfondir sa connaissance du langage. Et en plus, c’est amusant !

Je viens de penser à celui-ci :

static void Test(out int x, out int y)
{
    x = 42;
    y = 123;
    Console.WriteLine (x == y);
}

Que pensez-vous que ce code affiche ? Pouvez-vous en être sûr ? Postez votre réponse dans les commentaires !

J’essaierai de poster plus de casse-têtes à l’avenir, si j’en trouve d’autres.

Posted in Casse-têtes. Tags: , . 8 Comments »

Personnaliser l’interpolation de chaine avec C# 6

Très mauvaisMauvaisMoyenBonExcellent (Pas encore de note) 
Loading...Loading...

L’une des principales nouveautés de C# 6 est l’interpolation de chaines de caractères, qui permet d’écrire ce genre de chose :

string text = $"{p.Name} was born on {p.DateOfBirth:D}";

Un aspect peu connu de cette fonctionnalité est qu’une chaine interpolée peut être traitée soit comme un String, soit comme un IFormattable, selon le contexte. Quand elle est convertie en IFormattable, cela crée un objet FormattableString qui implémente l’interface et expose :

  • la chaine de format, avec les valeurs remplacées par des marqueurs numériques (compatible avec String.Format)
  • les valeurs pour les marqueurs

La méthode ToString() de cet objet appelle simplement String.Format(format, values). Mais il y a aussi une surcharge qui accepte un IFormatProvider, et c’est là que ça devient intéressant, parce que cela permet de personnaliser la façon dont les valeurs sont formatées. Il n’est peut-être pas évident de voir en quoi c’est utile, donc laissez moi vous montrer quelques exemples…

Spécifier la culture

Pendant la conception de la fonctionnalité d’interpolation de chaines, il y a eu un débat assez vif pour décider s’il fallait utiliser la culture courante ou la culture neutre (“invariant”) pour formater les valeurs; il y avait de bons arguments des deux côtés, mais au final il a été décidé d’utiliser la culture courante, par souci de cohérence avec String.Format et des APIs similaires qui utilisent la mise en forme composite. Utiliser la culture courante est pertinent quand on utilise l’interpolation de chaines pour construire des chaines qui seront affichée dans l’interface utilisateur ; mais il y a aussi des scénarios où on veut construire des chaines qui seront utilisées dans des APIs ou protocoles (URLs, requêtes SQL…), et dans ces cas là il faut généralement utiliser la culture neutre.

C# 6 fournit un moyen facile de faire cela, en tirant parti de la conversion en IFormattable. Il suffit de créer une méthode comme celle-ci :

static string Invariant(FormattableString formattable)
{
    return formattable.ToString(CultureInfo.InvariantCulture);
}

Et vous pouvez ensuite l’utiliser comme suit:

string text = Invariant($"{p.Name} was born on {p.DateOfBirth:D}");

Les valeurs dans la chaine interpolée seront formatées avec la culture neutre, et non plus avec la culture courante.

Construire des URLs

Voici un exemple plus avancé. L’interpolation de chaines est un moyen pratique de construire des URLs, mais si on inclut des chaines arbitraires dans l’URL, il faut prendre soin de les encoder pour ne pas avoir de caractères invalides dans l’URL. Un interpolateur de chaine personnalisé peut le faire pour nous; il faut juste créer un IFormatProvider personnalisé qui s’occupera d’encoder les valeurs. L’implémentation n’était pas évidente au premier abord, mais après quelques tâtonnements je suis arrivé à ceci :

class UrlFormatProvider : IFormatProvider
{
    private readonly UrlFormatter _formatter = new UrlFormatter();

    public object GetFormat(Type formatType)
    {
        if (formatType == typeof(ICustomFormatter))
            return _formatter;
        return null;
    }

    class UrlFormatter : ICustomFormatter
    {
        public string Format(string format, object arg, IFormatProvider formatProvider)
        {
            if (arg == null)
                return string.Empty;
            if (format == "r")
                return arg.ToString();
            return Uri.EscapeDataString(arg.ToString());
        }
    }
}

Ce formateur peut être utiliser comme ceci :

static string Url(FormattableString formattable)
{
    return formattable.ToString(new UrlFormatProvider());
}

...

string url = Url($"http://foobar/item/{id}/{name}");

Cela va correctement encoder les valeurs de id et name de façon à ce que l’’URL générée ne contienne que des caractères valides.

Aparté: Avez-vous remarqué le if (format == "r") ? C’est un spécificateur de format personnalisé qui indique que la valeur ne doit pas être encodé (“r” pour “raw”). Pour l’utiliser, il suffit de l’inclure dans la chaine de format comme ceci : {id:r}. Cela empêchera l’encodage de id.

Construire des requêtes SQL

On peut faire quelque chose de similaire pour les requêtes SQL. Bien sûr, intégrer des valeurs directement dans une requête est une mauvaise pratique bien connue, pour des raison de sécurité et de performance (il faut utiliser des requêtes paramétrées); mais pour un développement “à l’arrache”, ça peut parfois être utile. Et puis c’est une bonne illustration de cette fonctionnalité. Pour intégrer des valeurs dans une requête SQL, il faut :

  • encadrer les chaines entre des apostrophes, et échapper les apostrophes à l’intérieur des chaines en les doublant
  • formater les dates en fonction de ce que le SGBD attend (généralement MM/dd/yyyy)
  • formater les nombres selon la culture neutre
  • remplacer les valeurs nulles par le littéral NULL.

(il y a probablement d’autres choses à prendre en compte, mais ce sont les plus évidentes).

On peut utiliser la même approche que pour les URLs, et créer un SqlFormatProvider :

class SqlFormatProvider : IFormatProvider
{
    private readonly SqlFormatter _formatter = new SqlFormatter();

    public object GetFormat(Type formatType)
    {
        if (formatType == typeof(ICustomFormatter))
            return _formatter;
        return null;
    }

    class SqlFormatter : ICustomFormatter
    {
        public string Format(string format, object arg, IFormatProvider formatProvider)
        {
            if (arg == null)
                return "NULL";
            if (arg is string)
                return "'" + ((string)arg).Replace("'", "''") + "'";
            if (arg is DateTime)
                return "'" + ((DateTime)arg).ToString("MM/dd/yyyy") + "'";
            if (arg is IFormattable)
                return ((IFormattable)arg).ToString(format, CultureInfo.InvariantCulture);
            return arg.ToString();
        }
    }
}

On peut ensuite utiliser ce formateur comme ceci :

static string Sql(FormattableString formattable)
{
    return formattable.ToString(new SqlFormatProvider());
}

...

string sql = Sql($"insert into items(id, name, creationDate) values({id}, {name}, {DateTime.Now})");

De cette façon les valeurs seront correctement formatées pour générer une requête SQL valide.

Utiliser l’interpolation de chaines quand on cible des versions plus anciennes de .NET

Comme c’est souvent le cas avec les fonctionnalités du langage qui exploitent des types du .NET Framework, il est possible d’utiliser cette fonctionnalité avec des versions plus anciennes de .NET qui n’ont pas la classe FormattableString ; il suffit de créer la classe soi-même dans le namespace approprié. En fait, il y a en l’occurrence deux classes à implémenter : FormattableString et FormattableStringFactory. Jon Skeet était apparemment très pressé d’essayer, et il a déjà donné un exemple avec le code pour ces classes :

using System;

namespace System.Runtime.CompilerServices
{
    public class FormattableStringFactory
    {
        public static FormattableString Create(string messageFormat, params object[] args)
        {
            return new FormattableString(messageFormat, args);
        }

        public static FormattableString Create(string messageFormat, DateTime bad, params object[] args)
        {
            var realArgs = new object[args.Length + 1];
            realArgs[0] = "Please don't use DateTime";
            Array.Copy(args, 0, realArgs, 1, args.Length);
            return new FormattableString(messageFormat, realArgs);
        }
    }
}

namespace System
{
    public class FormattableString
    {
        private readonly string messageFormat;
        private readonly object[] args;

        public FormattableString(string messageFormat, object[] args)
        {
            this.messageFormat = messageFormat;
            this.args = args;
        }
        public override string ToString()
        {
            return string.Format(messageFormat, args);
        }
    }
}

C’est la même approche qui permettait d’utiliser Linq en ciblant .NET 2.0 (LinqBridge) ou les attributs d’infos de l’appelant quand on cible .NET 4.0 ou plus ancien. Bien sûr, ça nécessite quand même le compilateur C# 6 pour fonctionner…

Conclusion

La conversion de chaines interpolées en IFormattable avait déjà été mentionnée il y a quelque temps, mais n’était pas encore implémentée dans Visual Studio 2015 CTP 5. La CTP 6 qui vient d’être publiée embarque une nouvelle version du compilateur qui inclut cette fonctionnalité, vous pouvez donc commencer à jouer avec ! Cette fonctionnalité rend l’interpolation de chaine très flexible, et je suis sûr que les gens vont trouver toutes sortes de cas d’utilisation auxquels je n’avais pas pensé.

Vous pouvez trouver le code des exemples ci-dessus sur GitHub.

Test unitaires asynchrones avec NUnit

Très mauvaisMauvaisMoyenBonExcellent (Pas encore de note) 
Loading...Loading...

Récemment, mon équipe et moi avons commencé à écrire des tests unitaires pour une application qui utilise beaucoup de code asynchrone. Nous avons utilisé NUnit (2.6) parce que nous le connaissions déjà bien, mais nous ne l’avions encore jamais utilisé pour tester du code asynchrone.

Supposons que le système à tester soit cette très intéressante classe Calculator :

    public class Calculator
    {
        public async Task<int> AddAsync(int x, int y)
        {
            // simulate long calculation
            await Task.Delay(100).ConfigureAwait(false);
            // the answer to life, the universe and everything.
            return 42;
        }
    }

(Indice: ce code contient un bug… 42 n’est pas toujours la réponse. Ça m’a fait un choc quand j’ai appris ça!)

Et voici un test unitaire pour la méthode AddAsync :

        [Test]
        public async void AddAsync_Returns_The_Sum_Of_X_And_Y()
        {
            var calculator = new Calculator();
            int result = await calculator.AddAsync(1, 1);
            Assert.AreEqual(2, result);
        }

async void vs. async Task

Avant même de lancer ce test, je me suis dit : Ça ne va pas marcher! une méthode async void va retourner immédiatement sur le premier await, NUnit va donc croire que le test est terminé alors que l’assertion n’a pas été exécutée, et le test va donc passer même si l’assertion échoue. J’ai donc changé la signature de la méthode en async Task, en me croyant très malin d’avoir évité ce piège…

        [Test]
        public async Task AddAsync_Returns_The_Sum_Of_X_And_Y()

Comme prévu, le test a échoué, ce qui confirme que NUnit sait gérer les tests asynchrones. J’ai corrigé la classe Calculator, et je n’y ai plus pensé. Jusqu’au jour où j’ai remarqué qu’un collègue écrivait ses tests avec async void. J’ai donc commencé à lui expliquer pourquoi ça ne pouvait pas marcher, et j’ai essayé de le lui démontrer en ajoutant une assertion qui échouerait… et à ma grande surprise, le test a échoué, prouvant que j’avais tort !

Etant d’une nature curieuse, j’ai aussitôt commencé à investiguer… Ma première idée a été de vérifier le SynchronizationContext courant, et en effet, j’ai vu que NUnit l’avant remplacé par une instance de NUnit.Framework.AsyncSynchronizationContext. Cette classe maintient une file des continuations qui sont postées dessus. Après que la méthode async void retourne (c’est-à-dire la première fois qu’on await une tâche qui n’est pas encore terminée), NUnit appelle la méthode WaitForPendingOperationsToComplete, qui exécute toute les continuations de la file, jusqu’à ce que celle-ci soit vide. C’est seulement là que le test sera considéré comme terminé.

La morale de cette histoire est donc que vous pouvez écrire des tests async void avec NUnit 2.6. Cela fonctionne aussi avec les delegates passés à Assert.Throws, qui peuvent avoir le modificateur async. Cela étant dit, ce n’est pas parce que vous pouvez le faire que c’est forcément une bonne idée… Les frameworks de test unitaire n’ont pas tous le même support pour cela, et la prochaine version de NUnit (la 3.0, encore en alpha), ne supportera pas les tests async void.

Donc, à moins que vous ne comptiez rester sur NUnit 2.6.4 ad vitam æternam, il vaut probablement toujours utiliser async Task dans vos tests unitaires.

Une nouvelle bibliothèque pour afficher des GIFs animés dans les applications XAML

Très mauvaisMauvaisMoyenBonExcellent (Pas encore de note) 
Loading...Loading...

Il y a quelques années, j’avais écrit un article qui montrait comment afficher un GIF animé en WPF. L’article incluait le code complet, et avait eu un certain succès, puisque WPF n’avait pas de support intégré pour les GIFs animés. Suite aux problèmes mentionnés dans les commentaires, j’ai apporté de nombreuses modifications au code dans l’article. Au bout d’un certain temps j’ai fini par trouver que ce n’était vraiment pas pratique, j’ai donc publié le code sur CodePlex (il a depuis déménagé vers GitHub) sous le nom WpfAnimatedGif, et j’ai commencé à le maintenir en tant que projet open-source. C’était mon premier projet open-source sérieux, et il a connu une certaine popularité.

Quand les signalement de bugs ont commencé à arriver, un problème sérieux a rapidement été mis en évidence : la bibliothèque consommait une énorme quantité de mémoire. Il y avait quelques fuites, que j’ai corrigées, mais au final le problème était inhérent au mode de fonctionnement de la librairie : elle préparait toutes les frames à l’avance, les gardait en mémoire, et les affichait chacune à leur tour à l’aide d’une animation WPF. Avoir toutes les frames pré-rendues en mémoire était raisonnable pour de petites images avec peu de frames, mais posait un vrai problème pour de grosses animations GIF avec de nombreuses frames.

Il aurait peut-être été possible de modifier le cœur de la bibliothèque pour utiliser une autre approche, mais il y avait d’autres problèmes auxquels je voulais m’attaquer. Par exemple, elle se reposait en grande partie sur les fonctionnalités de manipulation d’image de WPF, ce qui rendait impossible le portage vers Windows Phone ou les apps Windows Store. De plus, certaines parties du code étaient assez complexes et inefficaces, en partie à cause de mon choix initial de spécifier l’image sous forme d’une ImageSource, et changer cela aurait cassé la compatibilité avec les versions précédentes.

WpfAnimatedGif est mort, vive XamlAnimatedGif !

J’ai donc décidé de recommencer de zéro pour traiter ces problèmes, et j’ai créé un nouveau projet : XamlAnimatedGif (comme vous le voyez, je manque un peu d’imagination pour les noms).

A première vue, cette nouvelle bibliothèque semble très similaire à WpfAnimatedGif, mais utilise en fait une approche complètement différente. Au lieu de préparer toutes les frames à l’avance, le rendu de chaque frame est fait à la volée à l’aide d’un WriteableBitmap. Cette approche sollicite plus le CPU, mais utilise beaucoup moins de mémoire. De plus, afin de permettre la portabilité, je ne pouvais pas utiliser les fonctions de décodage de WPF, j’ai donc dû implémenter un décodeur GIF complet, y compris la décompression LZW des données de pixels. L’article de Matthew Flickinger “What’s In A GIF” a été une aide précieuse sur ce point.

L’utilisation basique est à peu près le même que pour WpfAnimatedGif : il suffit d’assigner une propriété attachée sur un contrôle Image pour spécifier la source de l’animation GIF :

<Image gif:AnimationBehavior.SourceUri="/images/working.gif" />

Voilà le résultat dans l’émulateur Windows Phone (oui, c’est un GIF animé qui représente un GIF animé… je suppose qu’on peut appeler ça un méta-GIF Winking smile) :

XamlAnimatedGif-WP_thumb

Contrairement à WpfAnimatedGif, la source est spécifiée comme une URI ou un Stream, plutôt qu’une ImageSource. Cela rend l’implémentation interne beaucoup plus simple et robuste.

XamlAnimatedGif fonctionne actuellement sur WPF 4.5, les applications Windows Store 8.1, et Windows Phone 8.1. Le support d’autres plateformes (WPF 4.0, Windows 8.0, Windows Phone 8.0, Windows Phone Silverlight 8.1, peut-être Silverlight 5) pourrait être ajouté, mais pour l’instant je me suis simplement concentré sur le faire fonctionner sur les plateformes XAML les plus récentes. Je ne sais pas très bien s’il serait possible de supporter iOS et Android, vu que je n’ai pas encore mis le nez dans Xamarin. Si vous voulez essayer, je serai ravi d’accepter des contributions.

La bibliothèque est encore en alpha parce qu’elle est nouvelle, mais pour l’instant elle semble raisonnablement stable. Vous pouvez l’installer depuis NuGet :

PM> Install-Package XamlAnimatedGif -Pre 
Posted in Librairies. Tags: , , . No Comments »

Optimiser ToArray et ToList en fournissant le nombre d’éléments

Très mauvaisMauvaisMoyenBonExcellent (Pas encore de note) 
Loading...Loading...

Les méthodes d’extension ToArray et ToList sont des moyens pratiques de matérialiser une séquence énumérable (par exemple une requête Linq). Cependant, quelque chose me chiffonne : ces deux méthodes sont très inefficaces si elles ne connaissent pas le nombre d’éléments dans la séquence (ce qui est presque toujours le cas quand on les utilise sur une requête Linq). Concentrons nous sur ToArray pour l’instant (ToList a quelques différences, mais le principe est essentiellement le même).

Pour faire simple, ToArray prend une séquence, et retourne un tableau qui contient tous les éléments de la séquence. Si la séquence implémente ICollection<T>, ToArray utilise la propriété Count pour allouer un tableau de la bonne taille, et copie les éléments dedans ; voici un exemple :

List<User> users = GetUsers();
User[] array = users.ToArray();

Dans ce scénario, ToArray est assez efficace. Maintenant, changeons ce code pour extraire les noms des utilisateurs :

List<User> users = GetUsers();
string[] array = users.Select(u => u.Name).ToArray();

Maintenant, l’argument de ToArray est un IEnumerable<User> renvoyé par Select. Il n’implémente pas ICollection<User>, donc ToArray ne connait pas le nombre d’éléments, et ne peut donc pas allouer un tableau de la bonne taille. Voilà donc ce qu’il fait :

  1. commencer par allouer un petit tableau (4 éléments dans l’implémentation actuelle)
  2. copier les éléments depuis la source vers le tableau jusqu’à ce que celui-ci soit plein
  3. s’il n’y a plus d’éléments dans la séquence, aller en 7
  4. sinon, allouer un nouveau tableau deux fois plus grand que le précédent
  5. copier les éléments de l’ancien tableau vers le nouveau
  6. répéter depuis l’étape 2
  7. si le tableau est plus long que le nombre d’éléments, le tronquer : allouer un nouveau tableau d’exactement la bonne taille, et y copier les éléments depuis le tableau précédent
  8. renvoyer le tableau

S’il n’y a que quelques éléments, tout ceci est assez indolore ; mais pour une très longue séquence, c’est très inefficace, à cause des nombreuses allocations et copies.

Ce qui est agaçant est que, bien souvent, on (le développeur) connait le nombre d’éléments de la source! Dans l’exemple ci-dessus, on n’utilise que Select, qui ne change pas le nombre d’éléments, donc on sait que c’est le même que dans la liste d’origine; mais ToArray ne le sait pas, car l’information a été perdue en cours de route. Si seulement on pouvait fournir cette information nous-mêmes…

Eh bien, c’est en fait très facile à faire : il suffit de créer une nouvelle méthode d’extension qui accepte le nombre d’éléments comme paramètre. Voilà à quoi elle pourrait ressembler :

public static TSource[] ToArray<TSource>(this IEnumerable<TSource> source, int count)
{
    if (source == null) throw new ArgumentNullException("source");
    if (count < 0) throw new ArgumentOutOfRangeException("count");
    var array = new TSource[count];
    int i = 0;
    foreach (var item in source)
    {
        array[i++] = item;
    }
    return array;
}

On peut maintenant optimiser notre exemple précédent de la façon suivante :

List<User> users = GetUsers();
string[] array = users.Select(u => u.Name).ToArray(users.Count);

Notez que si vous spécifiez un nombre inférieur au nombre réel d’éléments dans la séquence, vous obtiendrez une IndexOutOfRangeException ; il est de votre responsabilité de fournir une information correcte à la méthode.

Et au final, qu’est-ce qu’on y gagne? D’après mes tests, cette version améliorée de ToArray est environ deux fois plus rapide que la version standard, pour une longue séquence (testé avec 1.000.000 éléments). Pas mal du tout !

Notez qu’on peut améliorer ToList de la même manière, en utilisant le constructeur de List<T> qui permet de spécifier la capacité initiale :

public static List<TSource> ToList<TSource>(this IEnumerable<TSource> source, int count)
{
    if (source == null) throw new ArgumentNullException("source");
    if (count < 0) throw new ArgumentOutOfRangeException("count");
    var list = new List<TSource>(count);
    foreach (var item in source)
    {
        list.Add(item);
    }
    return list;
}

Ici, le gain de performance n’est pas aussi important que pour ToArray (environ 25% au lieu de 50%), probablement parce que la liste n’a pas besoin d’être tronquée, mais il n’est pas négligeable.

Bien sûr, une optimisation similaire pourrait être faite pour ToDictionary, puisque la classe Dictionary<TKey, TValue> a aussi un constructeur qui permet de spécifier la capacité initiale.

Les méthodes ToArray et ToList améliorées sont disponibles dans ma librairie Linq.Extras, qui fournit également de nombreuses méthodes d’extension utiles pour travailler avec des séquences et collections.

Afficher facilement une taille de fichier sous forme lisible par un humain

Très mauvaisMauvaisMoyenBonExcellent (Pas encore de note) 
Loading...Loading...

Si vous écrivez une application qui a un rapport avec la gestion de fichiers, vous aurez probablement besoin d’afficher la taille des fichiers. Mais si un fichier a une taille de 123456789 octets, ce n’est évidemment pas la valeur qu’il faudra afficher, car c’est difficile à lire, et l’utilisateur n’a généralement pas besoin de connaitre la taille à l’octet près. Vous allez plutôt afficher quelque chose comme 118 Mo.

Ca ne devrait a priori pas être très compliqué, mais en fait il y a différentes façons d’afficher une taille en octets… Par exemple, plusieurs conventions coexistent pour les unités et préfixes :

  • La convention SI (Système International d’Unités) utilise des multiples décimaux, basés sur des puissances de 10 : 1 kilooctet vaut 1000 octets, 1 mégaoctet vaut 1000 kilooctets, etc. Les préfixes sont ceux du système métrique (k, M, G, etc.).
  • La convention CEI  (Commission Electrotechnique Internationale) utilise des multiples binaires, basés sur des puissances de 2 : 1 kibioctet vaut 1024 octets, 1 mébioctet vaut 1024 kibioctets, etc. Les préfixes sont Ki, Mi, Gi, etc., pour éviter la confusion avec le système métrique.
  • Mais aucune de ces conventions n’est communément utilisée : la convention usuelle est d’utiliser des multiples binaires (1024), mais des préfixes décimaux (K, M, G, etc.).

Selon le contexte, on utilisera l’une ou l’autre de ces conventions. Je n’ai jamais vu la convention SI utilisée où que ce soit ; certaines applications (je l’ai vu dans VirtualBox par exemple) utilisent la convention CEI ; la plupart des applications et systèmes d’exploitation utilisent la convention usuelle. Vous pouvez lire cet article Wikipédia si vous voulez en savoir plus : Préfixe binaire.

OK, alors supposons qu’on a choisi la convention usuelle pour l’instant. Maintenant, il faut décider quelle échelle utiliser : voulez-vous écrire 0,11 Go, 118 Mo, 120564 Ko ou 123456789 o ? Habituellement, on choisit l’échelle de façon à ce que la valeur affichée soit entre 1 et 1024.

Il y a encore quelques autres éléments à prendre en compte :

  • Voulez-vous afficher des valeurs entières, ou inclure quelques chiffres après la virgule ?
  • Y a-t-il une unité minimale à utiliser (par exemple, Windows n’affiche jamais des octets : un fichier d’1 octet est affiché comme 1 Ko) ?
  • Comment la valeur doit-elle être arrondie ?
  • Comment faut-il formater la valeur ?
  • Pour les valeurs inférieures à 1 Ko, voulez vous utiliser le mot “octets”, ou juste le symbole “o” ?

Bon, ça suffit! Où veux-tu en venir?

Comme vous pouvez le voir, afficher une taille en octets sous forme lisible par des humains n’est pas aussi évident qu’on aurait pu s’y attendre… J’ai eu à écrire du code pour le faire dans plusieurs applications, et j’ai fini par en avoir assez de refaire à chaque fois, donc j’ai créé une librairie qui s’efforce de couvrir tous les cas d’utilisation. Je l’ai appelée HumanBytes, pour des raisons qui devraient être évidentes… Elle est également disponible sous forme de package NuGet.

Son utilisation est assez simple. Elle est basée sur la classe ByteSizeFormatter, qui expose des propriétés pour contrôler la façon dont la valeur est formatée :

var formatter = new ByteSizeFormatter
{
    Convention = ByteSizeConvention.Binary,
    DecimalPlaces = 1,
    NumberFormat = "#,##0.###",
    MinUnit = ByteSizeUnit.Kilobyte,
    MaxUnit = ByteSizeUnit.Gigabyte,
    RoundingRule = ByteSizeRounding.Closest,
    UseFullWordForBytes = true,
};

var f = new FileInfo("TheFile.jpg");
Console.WriteLine("The size of '{0}' is {1}", f, formatter.Format(f.Length));

Cependant, dans la plupart des cas, vous voudrez simplement utiliser les paramètres par défaut. Vous pouvez le faire facilement grâce à la méthode d’extension Bytes :

var f = new FileInfo("TheFile.jpg");
Console.WriteLine("The size of '{0}' is {1}", f, f.Length.Bytes());

Cette méthode renvoie une instance de la structure ByteSize, dont la méthode ToString formate la valeur avec le formateur par défaut. Vous pouvez changer les paramètres du formateur par défaut via la propriété statique ByteSizeFormatter.Default.

A propos de la localisation

Toutes les langues n’utilisent pas le même symbole pour “octet”, et bien sûr le mot “octet” lui-même est différent d’une langue à l’autre. Pour l’instant, HumanBytes ne supporte que l’anglais et le français ; si vous voulez ajouter le support d’une autre langue, n’hésitez pas à forker le projet, ajouter votre traduction, et faire une pull request. Il n’y a que 3 termes à traduire, donc ça ne devrait pas prendre trop longtemps Winking smile.

Posted in Librairies. Tags: , , , , . No Comments »

StringTemplate: une autre approche de l’interpolation de chaines

Très mauvaisMauvaisMoyenBonExcellent (Pas encore de note) 
Loading...Loading...

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.

Passage de paramètres par référence à une méthode asynchrone

Très mauvaisMauvaisMoyenBonExcellent (1 votes) 
Loading...Loading...

L’asynchronisme dans C# est une fonctionnalité géniale, et je l’ai beaucoup utilisé depuis son apparition. Mais il y a quelques limitations agaçantes; par exemple, on ne peut pas passer des paramètres par référence (ref ou out) à une méthode asynchrone. Il y a de bonnes raisons pour cela; la plus évidente est que si vous passez par référence une variable locale, elle est stockée sur la pile, or la pile ne va pas rester disponible pendant toute l’exécution de la méthode asynchone (seulement jusqu’au premier await), donc l’emplacement de la variable n’existera plus.

Cependant, cette limitation est assez facile à contourner : il suffit de créer une classe Ref<T> pour encapsuler la valeur, et de passer une instance de cette classe par valeur à la méthode asynchrone:

async void btnFilesStats_Click(object sender, EventArgs e)
{
    var count = new Ref<int>();
    var size = new Ref<ulong>();
    await GetFileStats(tbPath.Text, count, size);
    txtFileStats.Text = string.Format("{0} files ({1} bytes)", count, size);
}

async Task GetFileStats(string path, Ref<int> totalCount, Ref<ulong> totalSize)
{
    var folder = await StorageFolder.GetFolderFromPathAsync(path);
    foreach (var f in await folder.GetFilesAsync())
    {
        totalCount.Value += 1;
        var props = await f.GetBasicPropertiesAsync();
        totalSize.Value += props.Size;
    }
    foreach (var f in await folder.GetFoldersAsync())
    {
        await GetFilesCountAndSize(f, totalCount, totalSize);
    }
}

La class Ref<T> ressemble à ceci:

public class Ref<T>
{
    public Ref() { }
    public Ref(T value) { Value = value; }
    public T Value { get; set; }
    public override string ToString()
    {
        T value = Value;
        return value == null ? "" : value.ToString();
    }
    public static implicit operator T(Ref<T> r) { return r.Value; }
    public static implicit operator Ref<T>(T value) { return new Ref<T>(value); }
}

Comme vous pouvez le voir, il n’y a rien de très compliqué. Cette approche peut également être utilisée pour les blocs itérateurs (yield return), qui n’autorisent pas non plus les paramètres ref ou out. Elle a aussi un avantage par rapport aux paramètres ref et out standards: elle permet de rendre le paramètre optionel, par exemple si on n’est pas intéressé par le résultat (évidemment il faut que la méthode appelée gère ce cas de façon appropriée).

Un moyen facile de tester unitairement la validation des arguments null

Très mauvaisMauvaisMoyenBonExcellent (Pas encore de note) 
Loading...Loading...

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).

css.php