Bienvenue à Blogs CodeS-SourceS Identification | Inscription | Aide

CoqBlog

.NET is good :-)
{ Blog de Gaël Covain }

Actualités

Une énumération (enum en C#) énumère, mais ne donne pas de garantie

S'il y a un aspect des énumérations qui est méconnu (et j'ai moi même fait ce type de mauvaise supposition par le passé), c'est bien le fait que l'utilisation d'un enum pour une propriété, un paramètre (etc) ne garanti PAS l'obtention d'une valeur valide de cette énumération.

Vous pouvez voir cette information en autres au travers de guidelines à propos de la définition d'énumération et de gestion de leur évolution, comme c'est le cas dans ce post de Krzysztof Cwalina, et dans le livre Framework Design Guidelines dont la seconde édition est sortie récemment.
Ces guidelines abordent divers sujets comme la nécessité de traiter de manière particulière la valeur 0, mais dans ce post nous allons nous pencher un peu plus sur cet aspect de non garantie de validité qu'il faut prendre en compte lors du développement d'API utilisant des énumérations, surtout si elle est publique.

 

 

Une énumération énumère, mais ne donne pas de garantie

Je pense que cette petite phrase peut aider à mémoriser ce caractère : "Une énumération énumère, mais ne donne pas de garantie"

En effet, le code suivant est tout à fait valide (tout du moins d'un point de vue compilation et exécution, mais côté logique ça peut coincer) :

class Program
{
  /// <summary>
  /// Liste des valeurs possibles.
  /// </summary>
  public enum ValeursPossibles : int
  {
    Un = 1,
    Deux = 2,
    Trois = 3
  }

  static void Main(string[] args)
  {
    // Normal
    DoSomething(ValeursPossibles.Un);
    DoSomething(ValeursPossibles.Deux);
    DoSomething(ValeursPossibles.Trois);

    // Déjà beaucoup moins normal
    DoSomething((ValeursPossibles)(-1));
    DoSomething((ValeursPossibles)(Int32.MinValue));
    DoSomething((ValeursPossibles)(Int32.MaxValue));

    // L'absence de l'attribut Flags n'exclu pas les opérations de ce type
    DoSomething(ValeursPossibles.Un | ValeursPossibles.Deux );
    DoSomething(ValeursPossibles.Un | (ValeursPossibles)256);
  }

  public static void DoSomething(ValeursPossibles valeur)
  {
    Console.WriteLine("DoSomething : {0} ({0:D})", valeur);
  }
}

Ce qui donne la sortie :

DoSomething : Un (1)
DoSomething : Deux (2)
DoSomething : Trois (3)
DoSomething : -1 (-1)
DoSomething : -2147483648 (-2147483648)
DoSomething : 2147483647 (2147483647)
DoSomething : Trois (3)
DoSomething : 257 (257)

L'utilisation d'une énumération ne vous libère pas de la validation des valeurs attendues.
Il faut voir l'énumération comme une simplification lors de la saisie/lecture du code et un moyen de réduire les erreurs involontaires par présentation d'une liste de valeurs possibles facilement identifiables, tant par les développeurs que par leurs outils (contrairement à des solutions du type constantes).

Elle n'empêchera en rien un utilisateur malintentionné de tenter de faire des dégâts.

 

 

Comment valider ?

L'utilisation de Enum.IsDefined pour vérifier simplement la liste des valeurs définies par l'énumération est déconseillée par certaines guidelines, comme justement celles citées plus haut.
Cette méthode est déconseillée tant pour un éventuel problème de coût à l'exécution que pour le fait qu'elle ne permet pas de gérer certains aspects de cette validation, notamment celles liées à l'évolution possible de l'enum sans pour autant que l'ensemble du code l'utilisant évolue lui aussi.

A mon avis en validant les entrées, il faut se poser ce type de questions :

  • est-ce que je dois autoriser n'importe quelle valeur définie dans l'énumération, y compris les ajouts futurs ?
  • est-ce que je dois autoriser n'importe quelle valeur définie dans l'énumération, mais pas les ajouts futurs ?
  • au moment d'écrire le code, une valeur de l'énumération est invalide dans mon cas : est ce que je dois autoriser n'importe quel ajout futur ?

Ce type de question va orienter votre choix de méthode de vérification, qui peut, je pense, être imagé de la manière suivante :

Diagramme illustrant le choix d'une méthode de vérification

"Nous maitrisons, personne chez nous ne passera de valeur invalide !"
Et si un jour ça venait à être exposé au travers d'un service web ou d'un service WCF ?
Avec une valeur invalide, les proxies générés devraient échouer dans leur tentative d'envoi, par contre un message bâti à la main pourrait passer (dans le cas d'un service web la simple page de test permet d'envoyer une valeur invalide).
Dans tous les cas, une validation uniquement côté client ne peut être considérée comme suffisante.

Une simple vérification par plage peut prendre cette forme :

public static void DoSomethingSafe(ValeursPossibles valeur)
{
  // Validation du paramètre valeur
  if (valeur < ValeursPossibles.Un || valeur > ValeursPossibles.Trois)
  {
    throw new InvalidEnumArgumentException(
      "valeur", 
      (Int32)valeur, 
      typeof(ValeursPossibles)
      );
  }

  Console.WriteLine("DoSomething : {0} ({0:D})", valeur);
}

 

 

Et si je levais une exception en cas de valeur invalide ?

Vous l'avez sans doute vu dans l'exemple ci-dessus, si vous souhaitez lever une exception en cas de spécification de valeur invalide à votre API, au lieu d'utiliser une simple ArgumentException vous pouvez opter pour un type d'exception plus spécifique : InvalidEnumArgumentException

Le type InvalidEnumArgumentException est défini dans l'assembly System.dll (contrairement à ArgumentException défini directement dans mscorlib.dll), sous l'espace de nom System.ComponentModel.

Ce post vous a plu ? Ajoutez le dans vos favoris pour ne pas perdre de temps à le retrouver le jour où vous en aurez besoin :
Posted: vendredi 20 février 2009 22:42 par coq
Classé sous : , ,

Commentaires

FREMYCOMPANY a dit :

Le comportement est identique sous VB. L'énum est juste une 'amélioration' de la syntaxe. Au final, on retombe sur le type Integer (ou autre si on définit une enum de type Byte, Int64, ...)

# février 21, 2009 12:32
Les commentaires anonymes sont désactivés

Les 10 derniers blogs postés

- Intégration Yammer et SharePoint Online (Office 365), étape 1 … par Le blog de Patrick [MVP SharePoint] le 06-12-2013, 17:37

- [Dynamics CRM] Ajouter les dossiers de CRM au dossier Favoris d’Outlook par Christine Dubois le 06-10-2013, 15:50

- Visual Studio 2013 par Etienne Margraff le 06-04-2013, 10:26

- Configurer la collation SQL Server pour SharePoint par Blog de Jérémy Jeanson le 06-03-2013, 19:48

- Etendre le Team Web Access de TFS 2012 – Step 1: Création du plugin par Philippe Didiergeorges Aka Philess le 06-03-2013, 07:30

- Livre Blanc : Développer des applications NUI par Fathi Bellahcene le 06-01-2013, 11:35

- [Dynamics CRM 2011] Copier une vue d'entité par Christine Dubois le 05-29-2013, 13:20

- [Conf’SharePoint 2013] Mes présentations… par Le blog de Patrick [MVP SharePoint] le 05-28-2013, 09:04

- [wpdev] Storage bug in MediaLibrary.SavePicture par Kévin Gosse le 05-26-2013, 19:08

- VMMap en mode instrumentation sur système 64bit : attention à la plateforme cible du build .NET par CoqBlog le 05-25-2013, 22:25