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 :
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 :
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 :
Merci à Johan de m’avoir fait remarqué ceci.
Très bien construit cet article!
Amis développeurs, faites en sorte que les exceptions soient exceptionelles 🙂
Johan, tu te prends pour Wanig ou tu lui as piqué sa blague !