101 votes

Valider les valeurs des Enum

J'ai besoin de valider un entier pour savoir si c'est une valeur d'énumération valide.

Quelle est la meilleure façon de faire cela en C# ?

1 votes

Pour une approche avec des drapeaux, il pourrait être utile de vérifier cette réponse sur une question dupliquée : stackoverflow.com/a/23177585/5190842

0 votes

Vérifiez EnumDataTypeAttribute

0 votes

Le moyen le plus rapide est de ne pas valider l'enum et d'utiliser le comportement par défaut, tout enum devrait avoir un membre comme "None" ou "Default" ou quelque chose comme ça. Vous pourriez utiliser le cas par défaut du switch.

108voto

Vman Points 498

Il faut aimer ces gens qui partent du principe que les données ne proviennent pas seulement d'une interface utilisateur, mais d'une interface utilisateur que vous contrôlez !

IsDefined convient pour la plupart des scénarios, vous pouvez commencer par.. :

public static bool TryParseEnum<TEnum>(this int enumValue, out TEnum retVal)
{
 retVal = default(TEnum);
 bool success = Enum.IsDefined(typeof(TEnum), enumValue);
 if (success)
 {
  retVal = (TEnum)Enum.ToObject(typeof(TEnum), enumValue);
 }
 return success;
}

(Évidemment, il suffit de laisser tomber le "ceci" si vous ne pensez pas qu'il s'agit d'une extension int appropriée).

16 votes

Veuillez également noter que Enum.IsDefined utilise la réflexion, ce qui peut entraîner des problèmes de performance.

2 votes

Ayant déjà vérifié IsDefined() pourquoi ne pas simplement convertir l'int dans la condition de succès : retVal = (TEnum)enumValue ?

1 votes

@jeromej Je demandais pourquoi on ne pouvait pas utiliser retVal = (TEnum)enumvalue au lieu de retVal = (TEnum)Enum.ToObject(typeof(TEnum), enumValue) dans la condition de réussite. Nous avons déjà confirmé que enumValue IsDefined() .

30voto

deegee Points 390

À mon avis, le message marqué comme étant la réponse est incorrect.
La validation des paramètres et des données est l'une des choses que l'on m'a inculquées il y a plusieurs décennies.

POURQUOI

La validation est nécessaire parce qu'il est possible d'attribuer n'importe quelle valeur entière à une énumération sans déclencher d'erreur.
J'ai passé de nombreux jours à faire des recherches sur la validation des enums en C#, car il s'agit d'une fonction nécessaire dans de nombreux cas.

Le principal objectif de la validation d'enum pour moi est de valider les données lues à partir d'un fichier : vous ne savez jamais si le fichier a été corrompu, s'il a été modifié de l'extérieur ou s'il a été piraté volontairement.
Et avec la validation par enum des données d'application collées à partir du presse-papiers : vous ne savez jamais si l'utilisateur a modifié le contenu du presse-papiers.

Cela dit, j'ai passé des jours à rechercher et à tester de nombreuses méthodes, y compris à établir le profil des performances de chaque méthode que j'ai pu trouver ou concevoir.

Les appels à n'importe quel élément de System.Enum sont si lents qu'ils pénalisent sensiblement les performances des fonctions contenant des centaines ou des milliers d'objets dont les propriétés contiennent un ou plusieurs enums dont les limites doivent être validées.

En résumé, restez à l'écart de tout dans la classe System.Enum lors de la validation des valeurs de l'enum, il est terriblement lent.

RÉSULTAT

La méthode que j'utilise actuellement pour la validation des enums va probablement faire lever les yeux au ciel à de nombreux programmeurs ici, mais c'est à mon avis la moins mauvaise pour la conception de mon application spécifique.

Je définis une ou deux constantes qui sont les limites supérieure et (éventuellement) inférieure de l'énumération, et je les utilise dans une paire d'instructions if() pour la validation.
L'inconvénient est que vous devez vous assurer de mettre à jour les constantes si vous modifiez l'enum.
Cette méthode ne fonctionne également que si l'énumération est un style "auto" où chaque élément de l'énumération est une valeur entière incrémentielle telle que 0,1,2,3,4,..... Elle ne fonctionnera pas correctement avec les drapeaux ou les énumérations dont les valeurs ne sont pas incrémentielles.

Notez également que cette méthode est presque aussi rapide que la méthode régulière if "<" ">" sur des int32 réguliers (qui a obtenu 38 000 ticks lors de mes tests).

Par exemple :

public const MyEnum MYENUM_MINIMUM = MyEnum.One;
public const MyEnum MYENUM_MAXIMUM = MyEnum.Four;

public enum MyEnum
{
    One,
    Two,
    Three,
    Four
};

public static MyEnum Validate(MyEnum value)
{
    if (value < MYENUM_MINIMUM) { return MYENUM_MINIMUM; }
    if (value > MYENUM_MAXIMUM) { return MYENUM_MAXIMUM; }
    return value;
}

PERFORMANCE

Pour ceux qui sont intéressés, j'ai profilé les variations suivantes sur une validation d'enum, et voici les résultats.

Le profilage a été effectué sur la compilation de la version dans une boucle d'un million de fois sur chaque méthode avec une valeur d'entrée entière aléatoire. Chaque test a été exécuté plus de 10 fois et la moyenne a été calculée. Les résultats des tics comprennent le temps total d'exécution, ce qui inclut la génération de nombres aléatoires, etc., mais ils sont constants pour tous les tests. 1 tick = 10ns.

Notez que le code ici n'est pas le code de test complet, il s'agit seulement de la méthode de base de validation des enums. De nombreuses autres variations ont été testées, et toutes ont donné des résultats similaires à ceux présentés ici, avec 1 800 000 ticks.

Classé du plus lent au plus rapide avec des résultats arrondis, en espérant qu'il n'y ait pas de fautes de frappe.

Limites déterminées par la méthode \= 13.600.000 ticks

public static T Clamp<T>(T value)
{
    int minimum = Enum.GetValues(typeof(T)).GetLowerBound(0);
    int maximum = Enum.GetValues(typeof(T)).GetUpperBound(0);

    if (Convert.ToInt32(value) < minimum) { return (T)Enum.ToObject(typeof(T), minimum); }
    if (Convert.ToInt32(value) > maximum) { return (T)Enum.ToObject(typeof(T), maximum); }
    return value;
}

Enum.IsDefined \= 1 800 000 ticks
Note : cette version du code ne fixe pas les valeurs Min/Max mais renvoie la valeur par défaut si elle est hors limites.

public static T ValidateItem<T>(T eEnumItem)
{
    if (Enum.IsDefined(typeof(T), eEnumItem) == true)
        return eEnumItem;
    else
        return default(T);
}

System.Enum Convertir Int32 avec des casts \= 1 800 000 ticks

public static Enum Clamp(this Enum value, Enum minimum, Enum maximum)
{
    if (Convert.ToInt32(value) < Convert.ToInt32(minimum)) { return minimum; }
    if (Convert.ToInt32(value) > Convert.ToInt32(maximum)) { return maximum; }
    return value;
}

if() Constantes Min/Max \= 43 000 ticks = le gagnant par 42x et 316x plus rapide.

public static MyEnum Clamp(MyEnum value)
{
    if (value < MYENUM_MINIMUM) { return MYENUM_MINIMUM; }
    if (value > MYENUM_MAXIMUM) { return MYENUM_MAXIMUM; }
    return value;
}

-eol-

12 votes

Un grand nombre de nos enums ne sont pas contigus, ce qui n'est pas une option dans de nombreux scénarios. Enum.IsDefined(typeof(T)' sera lent dans votre scénario de test car .net fait beaucoup de réflexion, boxing, etc. L'appel de cette fonction à chaque fois que vous analysez une ligne dans votre fichier d'importation ne sera jamais rapide. Si les performances sont la clé, j'envisagerais d'appeler Enum.GetValues une fois au début de l'importation. Ce ne sera jamais aussi rapide qu'une simple comparaison <> mais vous savez que cela fonctionnera pour tous les enums. Alternativement, vous pourriez avoir un parseur d'enum plus intelligent, je vais poster une autre réponse car il n'y a pas l'espace pour répondre proprement !

2 votes

@johnny 5 - D'après les informations ci-dessus : Cette méthode ne fonctionne également que si l'énumération est un style "auto" où chaque élément de l'énumération est une valeur entière incrémentale telle que 0,1,2,3,4,..... Elle ne fonctionnera pas correctement avec les drapeaux ou les énumérations dont les valeurs ne sont pas incrémentielles.

0 votes

@Vman - "Appeler ceci chaque fois que vous analysez une ligne dans votre fichier d'importation ne sera jamais rapide." - Ce sera nettement plus rapide que la vitesse de lecture du disque.

9voto

Jon Limjap Points 46429

Brad Abrams met spécifiquement en garde contre Enum.IsDefined dans son message Le danger de la simplification à outrance .

La meilleure façon de se débarrasser de cette exigence (c'est-à-dire de la nécessité de valider les enums) est de supprimer les moyens par lesquels les utilisateurs peuvent se tromper, par exemple, un champ de saisie quelconque. Utilisez des enums avec des listes déroulantes, par exemple, pour imposer uniquement les enums valides.

32 votes

La recommandation de ne mettre que des enums dans les listes déroulantes fonctionne bien pour WinForms, mais échoue pour WebForms, où vous devez valider contre les entrées malveillantes.

7 votes

Mon entrée provient d'un fichier XML produit par l'un des nombreux programmes possibles que je ne contrôle pas et dont la qualité des données varie considérablement. Enum.IsDefined est-il vraiment si mauvais parce qu'il semble le meilleur pour moi dans cette situation ?

0 votes

Richard Comme pour toute chose dans la vie, il y a des cas spécifiques où ce qui est généralement une mauvaise idée sera la solution appropriée pour cette situation. Si vous pensez que c'est la meilleure solution pour votre cas, allez-y :) Même les singletons, les variables globales et les ifs imbriqués sont une bonne idée pour certaines situations...

7voto

Vman Points 498

Cette réponse est une réponse à la réponse de Deegee qui soulève les problèmes de performance de System.Enum. Elle ne doit donc pas être considérée comme ma réponse générique préférée, qui concerne plutôt la validation des enums dans des scénarios de performance serrés.

Si vous avez un problème de performance critique où un code lent mais fonctionnel est exécuté dans une boucle serrée, je chercherais personnellement à déplacer ce code hors de la boucle si possible au lieu de le résoudre en réduisant la fonctionnalité. Contraindre le code à ne prendre en charge que les enums contigus peut être un cauchemar pour trouver un bogue si, par exemple, quelqu'un décide à l'avenir de déprécier certaines valeurs d'enum. De façon simpliste, vous pourriez appeler Enum.GetValues une seule fois, dès le début, pour éviter de déclencher toutes les réflexions, etc. des milliers de fois. Cela devrait vous donner une augmentation immédiate des performances. Si vous avez besoin de plus de performances et que vous savez que beaucoup de vos enums sont contigus (mais que vous voulez quand même supporter les enums "gappy"), vous pouvez aller un peu plus loin et faire quelque chose comme ceci :

public abstract class EnumValidator<TEnum> where TEnum : struct, IConvertible
{
    protected static bool IsContiguous
    {
        get
        {
            int[] enumVals = Enum.GetValues(typeof(TEnum)).Cast<int>().ToArray();

            int lowest = enumVals.OrderBy(i => i).First();
            int highest = enumVals.OrderByDescending(i => i).First();

            return !Enumerable.Range(lowest, highest).Except(enumVals).Any();
        }
    }

    public static EnumValidator<TEnum> Create()
    {
        if (!typeof(TEnum).IsEnum)
        {
            throw new ArgumentException("Please use an enum!");
        }

        return IsContiguous ? (EnumValidator<TEnum>)new ContiguousEnumValidator<TEnum>() : new JumbledEnumValidator<TEnum>();
    }

    public abstract bool IsValid(int value);
}

public class JumbledEnumValidator<TEnum> : EnumValidator<TEnum> where TEnum : struct, IConvertible
{
    private readonly int[] _values;

    public JumbledEnumValidator()
    {
        _values = Enum.GetValues(typeof (TEnum)).Cast<int>().ToArray();
    }

    public override bool IsValid(int value)
    {
        return _values.Contains(value);
    }
}

public class ContiguousEnumValidator<TEnum> : EnumValidator<TEnum> where TEnum : struct, IConvertible
{
    private readonly int _highest;
    private readonly int _lowest;

    public ContiguousEnumValidator()
    {
        List<int> enumVals = Enum.GetValues(typeof (TEnum)).Cast<int>().ToList();

        _lowest = enumVals.OrderBy(i => i).First();
        _highest = enumVals.OrderByDescending(i => i).First();
    }

    public override bool IsValid(int value)
    {
        return value >= _lowest && value <= _highest;
    }
}

Où votre boucle devient quelque chose comme :

//Pre import-loop
EnumValidator< MyEnum > enumValidator = EnumValidator< MyEnum >.Create();
while(import)   //Tight RT loop.
{
    bool isValid = enumValidator.IsValid(theValue);
}

Je suis sûr que les classes EnumValidator pourraient être écrites plus efficacement (c'est juste un hack rapide pour démontrer) mais franchement, qui se soucie de ce qui se passe en dehors de la boucle d'importation ? La seule partie qui doit être super rapide est à l'intérieur de la boucle. C'est la raison pour laquelle nous avons choisi la voie de la classe abstraite, pour éviter un if-enumContiguous-then-else inutile dans la boucle (la factory Create fait essentiellement cela en amont). Vous noterez un peu d'hypocrisie, par souci de brièveté, ce code limite la fonctionnalité aux int-enums. Je devrais utiliser IConvertible plutôt que d'utiliser directement des int, mais cette réponse est déjà assez verbeuse !

0 votes

Pour les plus grandes énumérations, vous pouvez utiliser un dictionnaire pour rechercher les valeurs de l'énumération en temps O(1).

1voto

Schultz9999 Points 2211

Voici comment je procède en me basant sur de nombreux articles en ligne. La raison pour laquelle je fais cela est de m'assurer que les enums marqués avec le symbole Flags peut également être validé avec succès.

public static TEnum ParseEnum<TEnum>(string valueString, string parameterName = null)
{
    var parsed = (TEnum)Enum.Parse(typeof(TEnum), valueString, true);
    decimal d;
    if (!decimal.TryParse(parsed.ToString(), out d))
    {
        return parsed;
    }

    if (!string.IsNullOrEmpty(parameterName))
    {
        throw new ArgumentException(string.Format("Bad parameter value. Name: {0}, value: {1}", parameterName, valueString), parameterName);
    }
    else
    {
        throw new ArgumentException("Bad value. Value: " + valueString);
    }
}

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X