Question

Je dois valider un entier pour savoir s'il s'agit d'une valeur d'énumération valide.

Quelle est la meilleure façon de procéder en C# ?

Était-ce utile?

La solution

Vous devez aimer ces gens qui supposent que les données proviennent non seulement toujours d’une interface utilisateur, mais d’une interface utilisateur sous votre contrôle !

IsDefined convient à 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, supprimez simplement « ceci » si vous ne pensez pas que ce soit une extension int appropriée)

Autres conseils

À mon humble avis, le message marqué comme la réponse est incorrecte.
La validation des paramètres et des données est l'une des choses qui m'ont été enseignées il y a des décennies.

POURQUOI

La validation est requise car pratiquement n'importe quelle valeur entière peut être attribuée à une énumération sans générer d'erreur.
J'ai passé de nombreux jours à rechercher la validation d'énumération C# car c'est une fonction nécessaire dans de nombreux cas.

Pour moi, l'objectif principal de la validation enum 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é en externe ou s'il a été piraté volontairement.
Et avec la validation enum des données d'application collées depuis le 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, notamment à dresser le profil des performances de chaque méthode que j'ai pu trouver ou concevoir.

Faire des appels vers quoi que ce soit dans System.Enum est si lent que cela a eu une incidence notable sur les performances des fonctions contenant des centaines ou des milliers d'objets comportant une ou plusieurs énumérations dans leurs propriétés qui devaient être validées pour les limites.

En fin de compte, restez à l'écart de tout dans la classe System.Enum lors de la validation des valeurs d'énumération, il est terriblement lent.

RÉSULTAT

La méthode que j'utilise actuellement pour la validation des énumérations attirera probablement les yeux de nombreux programmeurs ici, mais c'est à mon humble avis le moindre mal 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.
Un inconvénient est que vous devez vous assurer de mettre à jour les constantes si vous modifiez l'énumération.
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,....Cela ne fonctionnera pas correctement avec les indicateurs 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 les int32 réguliers (qui ont 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 variantes suivantes sur une validation enum, et voici les résultats.

Le profilage a été effectué lors de 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 calculé en moyenne.Les résultats du tick incluent le temps total d'exécution qui inclura la génération de nombres aléatoires, etc.mais ceux-ci seront constants à travers les tests.1 tick = 10ns.

Notez que le code ici n'est pas le code de test complet, il s'agit uniquement de la méthode de validation d'énumération de base.De nombreuses variantes supplémentaires ont également été testées, et toutes ont donné des résultats similaires à ceux présentés ici sur 1 800 000 ticks.

Répertorié du plus lent au plus rapide avec des résultats arrondis, espérons-le sans fautes de frappe.

Limites déterminées dans 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 de code ne se limite pas à 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 Convert Int32 avec conversions = 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 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-

Comme d'autres l'ont mentionné, Enum.IsDefined est lent, ce dont vous devez être conscient s'il est en boucle.

Lorsque vous effectuez plusieurs comparaisons, une méthode plus rapide consiste à commencer par placer les valeurs dans un HashSet.Ensuite, utilisez simplement Contains pour vérifier si la valeur est valide, comme ceci :

int userInput = 4;
// below, Enum.GetValues converts enum to array. We then convert the array to hashset.
HashSet<int> validVals = new HashSet<int>((int[])Enum.GetValues(typeof(MyEnum)));
// the following could be in a loop, or do multiple comparisons, etc.
if (validVals.Contains(userInput))
{
    // is valid
}

Brad Abrams met spécifiquement en garde contre Enum.IsDefined dans son message Le danger d’une simplification excessive.

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

Cette réponse est en réponse à la réponse de deegee qui soulève les problèmes de performances de System.Enum et ne doit donc pas être considérée comme ma réponse générique préférée, abordant davantage la validation d'énumération dans des scénarios de performances serrés.

Si vous rencontrez un problème de performances critique où un code lent mais fonctionnel est exécuté en boucle étroite, j'envisagerais personnellement de retirer ce code de la boucle si possible au lieu de le résoudre en réduisant les fonctionnalités.Contraindre le code à ne prendre en charge que les énumérations contiguës pourrait être un cauchemar pour trouver un bogue si, par exemple, quelqu'un décide à l'avenir de déprécier certaines valeurs d'énumération.De manière simple, vous pouvez simplement appeler Enum.GetValues ​​une fois, dès le début pour éviter de déclencher toute la réflexion, 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 énumérations sont contiguës (mais que vous souhaitez toujours prendre en charge les énumérations « gappy »), vous pouvez aller plus loin et faire quelque chose comme :

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 à démontrer) mais franchement, peu importe ce qui se passe en dehors de la boucle d'importation ?Le seul élément qui doit être ultra-rapide se trouve dans la boucle.C'est la raison pour laquelle nous avons choisi la route de la classe abstraite, pour éviter un if-enumContiguous-then-else inutile dans la boucle (l'usine Create le fait essentiellement à l'avance).Vous remarquerez un peu d'hypocrisie, par souci de concision, ce code limite les fonctionnalités aux int-énumérations.Je devrais utiliser IConvertible plutôt que d'utiliser directement les int, mais cette réponse est déjà assez verbeuse !

C'est ainsi que je procède en me basant sur plusieurs publications en ligne.La raison de cela est de s'assurer que les énumérations marquées de Flags L'attribut 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);
    }
}

j'ai trouvé ça lien cela y répond assez bien.Il utilise:

(ENUMTYPE)Enum.ToObject(typeof(ENUMTYPE), INT)

J'ai opté pour cette solution qui allie performances, commodité et maintenabilité.

public enum DogBreed
{
    Unknown = 0,
    Beagle = 1,
    Labrador = 2,
    PeruvianIncaOrchid = 3,
}
public static class DogBreedExtensions
{
    public static bool IsValidDogBreed(this DogBreed breed)
    {
        var v = (int)breed;
        return v >= 1 && v <= 3;
    }
}

Et utilisez-le ainsi :

var goodInput = 2;
var goodDog = (DogBreed)goodInput;
goodDog.IsValidDogBreed(); // true.

var badInput = 7;
var badDog = (DogBreed)badInput; // no problem, bad data here we come.
badDog.IsValidDogBreed(); // false, you're in the doghouse!

Dans mon cas, appeler Enum.IsDefined ne me mènerait qu'à mi-chemin, car je souhaite généralement rejeter la valeur d'énumération "Inconnu".

Cette approche évite les problèmes de performances d'Enum.IsDefined et définit la règle de validation proche de l'énumération, ce que j'aime.

La méthode d'extension est facilement adaptable aux exigences changeantes et peut être appliquée à int si vous le souhaitez - (c'est-à-dire public static bool IsValidDogBreed(this int breed))

Voici une solution générique rapide, utilisant un modèle construit statiquement HashSet<T>.

Vous pouvez le définir une fois dans votre boîte à outils, puis l'utiliser pour toute votre validation d'énumération.

public static class EnumHelpers
{
    /// <summary>
    /// Returns whether the given enum value is a defined value for its type.
    /// Throws if the type parameter is not an enum type.
    /// </summary>
    public static bool IsDefined<T>(T enumValue)
    {
        if (typeof(T).BaseType != typeof(System.Enum)) throw new ArgumentException($"{nameof(T)} must be an enum type.");

        return EnumValueCache<T>.DefinedValues.Contains(enumValue);
    }

    /// <summary>
    /// Statically caches each defined value for each enum type for which this class is accessed.
    /// Uses the fact that static things exist separately for each distinct type parameter.
    /// </summary>
    internal static class EnumValueCache<T>
    {
        public static HashSet<T> DefinedValues { get; }

        static EnumValueCache()
        {
            if (typeof(T).BaseType != typeof(System.Enum)) throw new Exception($"{nameof(T)} must be an enum type.");

            DefinedValues = new HashSet<T>((T[])System.Enum.GetValues(typeof(T)));
        }
    }
}

Notez que cette approche est également facilement étendue à l'analyse d'énumérations, en utilisant un dictionnaire avec des clés de chaîne (en tenant compte de l'insensibilité à la casse et des représentations de chaînes numériques).

Pour valider si une valeur est une valeur valide dans une énumération, il vous suffit d'appeler la méthode statique Enum.IsDefined.

int value = 99;//Your int value
if (Enum.IsDefined(typeof(your_enum_type), value))
{
   //Todo when value is valid
}else{
   //Todo when value is not valid
}
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top