Existe-t-il un moyen de vérifier si int est une énumération légale en C #?

167

J'ai lu quelques articles SO et il semble que la plupart des opérations de base manquent.

public enum LoggingLevel
{
    Off = 0,
    Error = 1,
    Warning = 2,
    Info = 3,
    Debug = 4,
    Trace = 5
};

if (s == "LogLevel")
{
    _log.LogLevel = (LoggingLevel)Convert.ToInt32("78");
    _log.LogLevel = (LoggingLevel)Enum.Parse(typeof(LoggingLevel), "78");
    _log.WriteDebug(_log.LogLevel.ToString());
}

Cela ne provoque aucune exception, il est heureux de stocker 78. Existe-t-il un moyen de valider une valeur entrant dans une énumération?

charme
la source
2
Double possible de Valider les valeurs d'énumération
Erik

Réponses:

271

Découvrez Enum.IsDefined

Usage:

if(Enum.IsDefined(typeof(MyEnum), value))
    MyEnum a = (MyEnum)value; 

Voici l'exemple de cette page:

using System;    
[Flags] public enum PetType
{
   None = 0, Dog = 1, Cat = 2, Rodent = 4, Bird = 8, Reptile = 16, Other = 32
};

public class Example
{
   public static void Main()
   {
      object value;     
      // Call IsDefined with underlying integral value of member.
      value = 1;
      Console.WriteLine("{0}: {1}", value, Enum.IsDefined(typeof(PetType), value));
      // Call IsDefined with invalid underlying integral value.
      value = 64;
      Console.WriteLine("{0}: {1}", value, Enum.IsDefined(typeof(PetType), value));
      // Call IsDefined with string containing member name.
      value = "Rodent";
      Console.WriteLine("{0}: {1}", value, Enum.IsDefined(typeof(PetType), value));
      // Call IsDefined with a variable of type PetType.
      value = PetType.Dog;
      Console.WriteLine("{0}: {1}", value, Enum.IsDefined(typeof(PetType), value));
      value = PetType.Dog | PetType.Cat;
      Console.WriteLine("{0}: {1}", value, Enum.IsDefined(typeof(PetType), value));
      // Call IsDefined with uppercase member name.      
      value = "None";
      Console.WriteLine("{0}: {1}", value, Enum.IsDefined(typeof(PetType), value));
      value = "NONE";
      Console.WriteLine("{0}: {1}", value, Enum.IsDefined(typeof(PetType), value));
      // Call IsDefined with combined value
      value = PetType.Dog | PetType.Bird;
      Console.WriteLine("{0:D}: {1}", value, Enum.IsDefined(typeof(PetType), value));
      value = value.ToString();
      Console.WriteLine("{0:D}: {1}", value, Enum.IsDefined(typeof(PetType), value));
   }
}

L'exemple affiche la sortie suivante:

//       1: True
//       64: False
//       Rodent: True
//       Dog: True
//       Dog, Cat: False
//       None: True
//       NONE: False
//       9: False
//       Dog, Bird: False
SwDevMan81
la source
@matti: Convertissez "78" en n'importe quelle représentation numérique utilisée LoggingLevelcomme stockage, puis présentez-la comme une LoggingLevelvaleur d'énumération.
thecoop
9
Il semble que cela IsDefinedne fonctionne pas pour les membres enum bitwisés.
Saeed Neamati
29

Les solutions ci-dessus ne traitent pas des [Flags]situations.

Ma solution ci-dessous peut avoir des problèmes de performances (je suis sûr que l'on pourrait optimiser de différentes manières) mais essentiellement, elle prouvera toujours si une valeur d'énumération est valide ou non .

Il repose sur trois hypothèses:

  • Les valeurs d'énumération en C # ne sont autorisées qu'à l'être int, absolument rien d'autre
  • Les noms d'énumération en C # doivent commencer par un caractère alphabétique
  • Aucun nom d'énumération valide ne peut être avec un signe moins: -

L'appel ToString()sur une énumération renvoie la intvaleur si aucune énumération (indicateur ou non) ne correspond. Si une valeur d'énumération autorisée correspond, il affichera le nom de la ou des correspondances.

Alors:

[Flags]
enum WithFlags
{
    First = 1,
    Second = 2,
    Third = 4,
    Fourth = 8
}

((WithFlags)2).ToString() ==> "Second"
((WithFlags)(2 + 4)).ToString() ==> "Second, Third"
((WithFlags)20).ToString() ==> "20"

Avec ces deux règles à l'esprit, nous pouvons supposer que si le .NET Framework fait correctement son travail, tout appel à une ToString()méthode d'énumération valide entraînera quelque chose qui a un caractère alphabétique comme premier caractère:

public static bool IsValid<TEnum>(this TEnum enumValue)
    where TEnum : struct
{
    var firstChar = enumValue.ToString()[0];
    return (firstChar < '0' || firstChar > '9') && firstChar != '-';
}

On pourrait appeler cela un "hack", mais les avantages sont qu'en se basant sur la propre implémentation de Microsoft Enumet sur les normes C #, vous ne vous fiez pas à votre propre code ou contrôles potentiellement bogués. Dans les situations où les performances ne sont pas exceptionnellement critiques, cela permettra d'économiser beaucoup de switchdéclarations désagréables ou d'autres vérifications!

Éditer

Merci à @ChaseMedallion d'avoir souligné que mon implémentation d'origine ne supportait pas les valeurs négatives. Cela a été corrigé et des tests ont été fournis.

Et les tests pour le sauvegarder:

[TestClass]
public class EnumExtensionsTests
{
    [Flags]
    enum WithFlags
    {
        First = 1,
        Second = 2,
        Third = 4,
        Fourth = 8
    }

    enum WithoutFlags
    {
        First = 1,
        Second = 22,
        Third = 55,
        Fourth = 13,
        Fifth = 127
    }

    enum WithoutNumbers
    {
        First, // 1
        Second, // 2
        Third, // 3
        Fourth // 4
    }

    enum WithoutFirstNumberAssigned
    {
        First = 7,
        Second, // 8
        Third, // 9
        Fourth // 10
    }


    enum WithNagativeNumbers
    {
        First = -7,
        Second = -8,
        Third = -9,
        Fourth = -10
    }

    [TestMethod]
    public void IsValidEnumTests()
    {
        Assert.IsTrue(((WithFlags)(1 | 4)).IsValid());
        Assert.IsTrue(((WithFlags)(1 | 4)).IsValid());
        Assert.IsTrue(((WithFlags)(1 | 4 | 2)).IsValid());
        Assert.IsTrue(((WithFlags)(2)).IsValid());
        Assert.IsTrue(((WithFlags)(3)).IsValid());
        Assert.IsTrue(((WithFlags)(1 + 2 + 4 + 8)).IsValid());

        Assert.IsFalse(((WithFlags)(16)).IsValid());
        Assert.IsFalse(((WithFlags)(17)).IsValid());
        Assert.IsFalse(((WithFlags)(18)).IsValid());
        Assert.IsFalse(((WithFlags)(0)).IsValid());

        Assert.IsTrue(((WithoutFlags)1).IsValid());
        Assert.IsTrue(((WithoutFlags)22).IsValid());
        Assert.IsTrue(((WithoutFlags)(53 | 6)).IsValid());   // Will end up being Third
        Assert.IsTrue(((WithoutFlags)(22 | 25 | 99)).IsValid()); // Will end up being Fifth
        Assert.IsTrue(((WithoutFlags)55).IsValid());
        Assert.IsTrue(((WithoutFlags)127).IsValid());

        Assert.IsFalse(((WithoutFlags)48).IsValid());
        Assert.IsFalse(((WithoutFlags)50).IsValid());
        Assert.IsFalse(((WithoutFlags)(1 | 22)).IsValid());
        Assert.IsFalse(((WithoutFlags)(9 | 27 | 4)).IsValid());

        Assert.IsTrue(((WithoutNumbers)0).IsValid());
        Assert.IsTrue(((WithoutNumbers)1).IsValid());
        Assert.IsTrue(((WithoutNumbers)2).IsValid());
        Assert.IsTrue(((WithoutNumbers)3).IsValid());
        Assert.IsTrue(((WithoutNumbers)(1 | 2)).IsValid()); // Will end up being Third
        Assert.IsTrue(((WithoutNumbers)(1 + 2)).IsValid()); // Will end up being Third

        Assert.IsFalse(((WithoutNumbers)4).IsValid());
        Assert.IsFalse(((WithoutNumbers)5).IsValid());
        Assert.IsFalse(((WithoutNumbers)25).IsValid());
        Assert.IsFalse(((WithoutNumbers)(1 + 2 + 3)).IsValid());

        Assert.IsTrue(((WithoutFirstNumberAssigned)7).IsValid());
        Assert.IsTrue(((WithoutFirstNumberAssigned)8).IsValid());
        Assert.IsTrue(((WithoutFirstNumberAssigned)9).IsValid());
        Assert.IsTrue(((WithoutFirstNumberAssigned)10).IsValid());

        Assert.IsFalse(((WithoutFirstNumberAssigned)11).IsValid());
        Assert.IsFalse(((WithoutFirstNumberAssigned)6).IsValid());
        Assert.IsFalse(((WithoutFirstNumberAssigned)(7 | 9)).IsValid());
        Assert.IsFalse(((WithoutFirstNumberAssigned)(8 + 10)).IsValid());

        Assert.IsTrue(((WithNagativeNumbers)(-7)).IsValid());
        Assert.IsTrue(((WithNagativeNumbers)(-8)).IsValid());
        Assert.IsTrue(((WithNagativeNumbers)(-9)).IsValid());
        Assert.IsTrue(((WithNagativeNumbers)(-10)).IsValid());
        Assert.IsFalse(((WithNagativeNumbers)(-11)).IsValid());
        Assert.IsFalse(((WithNagativeNumbers)(7)).IsValid());
        Assert.IsFalse(((WithNagativeNumbers)(8)).IsValid());
    }
}
Joshcomley
la source
1
Merci pour cela, j'ai eu un problème similaire concernant les combinaisons de drapeaux valides. Au lieu de vérifier le premier caractère de l'énumération, vous pouvez également essayer d'int.TryParse (enumValue.ToString ()) ... En cas d'échec, vous disposez d'un ensemble d'indicateurs valides. Cela peut cependant être plus lent que votre solution.
MadHenchbot
Cette implémentation ne parvient pas à valider correctement les valeurs négatives, car la vérification concerne les caractères non numériques
ChaseMedallion
Bonne prise!! Je vais mettre à jour ma réponse pour accueillir un tel, merci @ChaseMedallion
joshcomley
J'aime le mieux cette solution, les astuces mathématiques présentées ne fonctionnent que si elles [Flags]ont des valeurs entières raisonnables.
MrLore
17

La réponse canonique serait Enum.IsDefined, mais c'est a: un peu lent s'il est utilisé dans une boucle serrée, et b: pas utile pour les [Flags]énumérations.

Personnellement, j'arrêterais de m'inquiéter à ce sujet et switch, de manière appropriée, de me souvenir:

  • si c'est OK de ne pas tout reconnaître (et de ne rien faire), alors n'ajoutez pas de default:(ou ayez un vide default:expliquant pourquoi)
  • s'il y a un comportement par défaut raisonnable, mettez-le dans le default:
  • sinon, gérez ceux que vous connaissez et lancez une exception pour le reste:

Ainsi:

switch(someflag) {
    case TriBool.Yes:
        DoSomething();
        break;
    case TriBool.No:
        DoSomethingElse();
        break;
    case TriBool.FileNotFound:
        DoSomethingOther();
        break;
    default:
        throw new ArgumentOutOfRangeException("someflag");
}
Marc Gravell
la source
pas familier avec les énumérations [Flags] et les performances ne sont pas un problème, donc votre réponse semble être la raison pour laquelle les énumérations ont été inventées en premier lieu;) en regardant vos "points" ou leur nom, vous devez avoir un point là . Je parie que vous ne les avez pas obtenus pour rien, mais pensez à la situation de lecture du fichier de configuration où il y a 257 valeurs dans une définition d'énumération. Sans parler des dizaines d'autres énumérations. Il y aurait beaucoup de rangées de cas ...
char m
@matti - cela semble un exemple extrême; la désérialisation est de toute façon un domaine spécialisé - la plupart des moteurs de sérialisation offrent la validation d'énumération gratuitement.
Marc Gravell
@matti - sur une note latérale; Je dirais de traiter les réponses en fonction de leurs mérites individuels. Je me trompe parfois complètement, et quelqu'un avec "rep 17" pourrait tout aussi bien donner une réponse parfaite .
Marc Gravell
La réponse du commutateur est rapide, mais n'est pas générique.
Eldritch Conundrum
8

Utilisation:

Enum.IsDefined ( typeof ( Enum ), EnumValue );
n535
la source
4

Pour faire face, [Flags]vous pouvez également utiliser cette solution de C # Cookbook :

Tout d'abord, ajoutez une nouvelle ALLvaleur à votre énumération:

[Flags]
enum Language
{
    CSharp = 1, VBNET = 2, VB6 = 4, 
    All = (CSharp | VBNET | VB6)
}

Ensuite, vérifiez si la valeur est dans ALL:

public bool HandleFlagsEnum(Language language)
{
    if ((language & Language.All) == language)
    {
        return (true);
    }
    else
    {
        return (false);
    }
}
Mario Levrero
la source
2

Une façon de faire serait de s'appuyer sur le cast et la conversion d'énumération en chaîne. Lors du transtypage de int en un type Enum, l'int est soit converti en une valeur d'énumération correspondante, soit l'énumération résultante contient simplement int comme valeur si la valeur enum n'est pas définie pour l'int.

enum NetworkStatus{
  Unknown=0,
  Active,
  Slow
}

int statusCode=2;
NetworkStatus netStatus = (NetworkStatus) statusCode;
bool isDefined = netStatus.ToString() != statusCode.ToString();

Non testé pour les cas extrêmes.

maulik13
la source
1

Comme les autres l'ont dit, Enum.IsDefinedretourne falsemême si vous avez une combinaison valide d'indicateurs de bits pour une énumération décorée avec le FlagsAttribute.

Malheureusement, le seul moyen de créer une méthode retournant true pour les indicateurs de bits valides est un peu long:

public static bool ValidateEnumValue<T>(T value) where T : Enum
{
    // Check if a simple value is defined in the enum.
    Type enumType = typeof(T);
    bool valid = Enum.IsDefined(enumType, value);
    // For enums decorated with the FlagsAttribute, allow sets of flags.
    if (!valid && enumType.GetCustomAttributes(typeof(FlagsAttribute), false)?.Any() == true)
    {
        long mask = 0;
        foreach (object definedValue in Enum.GetValues(enumType))
            mask |= Convert.ToInt64(definedValue);
        long longValue = Convert.ToInt64(value);
        valid = (mask & longValue) == longValue;
    }
    return valid;
}

Vous pouvez mettre en cache les résultats de GetCustomAttributedans un dictionnaire:

private static readonly Dictionary<Type, bool> _flagEnums = new Dictionary<Type, bool>();
public static bool ValidateEnumValue<T>(T value) where T : Enum
{
    // Check if a simple value is defined in the enum.
    Type enumType = typeof(T);
    bool valid = Enum.IsDefined(enumType, value);
    if (!valid)
    {
        // For enums decorated with the FlagsAttribute, allow sets of flags.
        if (!_flagEnums.TryGetValue(enumType, out bool isFlag))
        {
            isFlag = enumType.GetCustomAttributes(typeof(FlagsAttribute), false)?.Any() == true;
            _flagEnums.Add(enumType, isFlag);
        }
        if (isFlag)
        {
            long mask = 0;
            foreach (object definedValue in Enum.GetValues(enumType))
                mask |= Convert.ToInt64(definedValue);
            long longValue = Convert.ToInt64(value);
            valid = (mask & longValue) == longValue;
        }
    }
    return valid;
}

Notez que le code ci-dessus utilise la nouvelle Enumcontrainte sur Tlaquelle n'est disponible que depuis C # 7.3. Vous devez passer un object valuedans les anciennes versions et l'appeler GetType().

Rayon
la source