TryParse pour Enum : ça existe, mais en .NET 4.0

La plupart d’entre vous savez que gérer une exception est quelque chose de très lent, et qui peut amener à des potentiels problèmes de performance. Prenons un bloc de code comme ceci :

decimal Resultat = 0;

Stopwatch Chrono = Stopwatch.StartNew();
try { Resultat = decimal.Parse("coucou"); }
catch { Resultat = -1; }
Chrono.Stop();
Console.WriteLine("Dans un try...catch : {0} ticks", Chrono.ElapsedTicks);

Chrono.Restart();
if (!decimal.TryParse("coucou", out Resultat))
    Resultat = -1;
Chrono.Stop();
Console.WriteLine("Avec un TryParse : {0} ticks", Chrono.ElapsedTicks);

Notez au passage qu’il faut bien affecter la valeur –1 en cas d’échec : comme la variable est précédée d’un mot-clé out dans la signature, cela signifie que c’est la fonction qui l’initialise, et elle le fait sur la valeur par défaut du type. Toute affectation préalable sera écrasée.

L’exécution ramènera quelque chose comme :

image

Il y a bien sûr un peu de variance en fonction de la machine, l’ordre dans lequel on exécute les deux tests, peut-être des effets de cache, etc. Mais en gros, le fait de traiter une exception pose un gros problème de temps, et c’est la raison pour laquelle la fonction TryParse existe : aller vite pour traiter, tout en ouvrant la porte à une possibilité relativement fréquente de chaîne non transformable dans le type qu’on cherche à attendre.

Bien sûr, si vous avez une certaine garantie que la chaîne est censée ne jamais être autre chose qu’un numérique, vous pouvez tout à fait utiliser Parse, et laisser les cas exceptionnels (c’est d’ailleurs bien pourquoi on appelle ceci des exceptions) être traités par le catch.

Au passage, même si on remplace la chaîne coucou pour une chaîne comme 12 qui ne provoque pas d’exception, on reste dans des rapports non négligeables :

image

La fonction TryParse existe aussi pour les entiers, les dates, etc. Mais curieusement, jusqu’à .NET 4.0, elle n’existait pas pour les énumérations. Du coup, on était obligé de réaliser ce genre de code, qui est bien sûr plus rapide que de traiter l’exception, mais qui n’est pas très élégant :

enum Mode { NORMAL = 0, RAPIDE = 1 };

static void Main(string[] args)
{
    Mode Resultat = 0;

    Stopwatch Chrono = Stopwatch.StartNew();
    try { Resultat = (Mode)Enum.Parse(typeof(Mode), "COUCOU"); }
    catch { Resultat = Mode.NORMAL ; }
    Chrono.Stop();
    Console.WriteLine("Dans un try...catch : {0} ticks", Chrono.ElapsedTicks);

    Chrono.Restart();
    Resultat = Mode.NORMAL;
    if (Enum.IsDefined(typeof(Mode), "COUCOU"))
        Resultat = (Mode)Enum.Parse(typeof(Mode), "COUCOU");
    Chrono.Stop();
    Console.WriteLine("Avec un TryParse : {0} ticks", Chrono.ElapsedTicks);
}

J’ai fait exprès ici de répéter la valeur COUCOU en dur, qui viendrait normalement d’une variable, pour faire apparaître le manque d’élégance de ce code : d’une certaine manière, on force .NET à travailler deux fois. Il doit d’abord vérifier que l’entrée existe dans l’énumération, puis si c’est le cas, refaire à peu près le même travail pour trouver cette valeur.

C’est un peu la même chose que l’utilisation d’un as au lieu d’un is :

// Code peu performant, car la conversion est réalisée potentiellement deux fois
if (Instance is Personne)
    ((Personne)Instance).Reveiller();

// Code plus élégant et performant
Personne p = Instance as Personne;
if (p != null)
    p.Reveiller();

Encore une petite note pour préciser qu’en plus, le second code est mieux résistant au cas où l’instance est nulle.

Heureusement, .NET 4.0 a corrigé ceci, avec un TryParse dans la classe Enum, et générique de surcroît, ce qui va nous éviter ces horreurs de cast…

Le bon code est donc celui-ci :

Mode Resultat = 0;

Stopwatch Chrono = Stopwatch.StartNew();
try { Resultat = (Mode)Enum.Parse(typeof(Mode), "COUCOU"); }
catch { Resultat = Mode.NORMAL ; }
Chrono.Stop();
Console.WriteLine("Dans un try...catch : {0} ticks", Chrono.ElapsedTicks);

Chrono.Restart();
if (!Enum.TryParse<Mode>("COUCOU", out Resultat))
    Resultat = Mode.NORMAL;
Chrono.Stop();
Console.WriteLine("Avec un TryParse : {0} ticks", Chrono.ElapsedTicks);

 

Il est à la fois élégant, et surtout très performant dans le cas où les chaînes sont invalides plus souvent que de manière absolument exceptionnelle :

image

Merci à Johan de m’avoir fait remarqué ceci.

About JP Gouigoux

Jean-Philippe Gouigoux est Architecte Logiciel, MVP Connected Systems Developer. Il intervient régulièrement à l'Université de Bretagne Sud ainsi qu'à l'Agile Tour. Plus de détails sur la page "Curriculum Vitae" de ce blog.
This entry was posted in .NET, C#. Bookmark the permalink.

2 Responses to TryParse pour Enum : ça existe, mais en .NET 4.0

  1. Johan says:

    Très bien construit cet article!
    Amis développeurs, faites en sorte que les exceptions soient exceptionelles 🙂

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Captcha Captcha Reload