C'è un modo per verificare se int è enum legale in C #?


167

Ho letto alcuni post SO e sembra che manchi la maggior parte delle operazioni di base.

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());
}

Ciò non fa eccezioni, è felice di memorizzarlo 78. C'è un modo per convalidare un valore andando in un enum?


2
Possibile duplicato di Validate Enum Values
Erik

Risposte:


271

Dai un'occhiata a Enum.IsDefined

Uso:

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

Questo è l'esempio da quella pagina:

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'esempio mostra il seguente output:

//       1: True
//       64: False
//       Rodent: True
//       Dog: True
//       Dog, Cat: False
//       None: True
//       NONE: False
//       9: False
//       Dog, Bird: False

@matti: converti "78" in qualunque rappresentazione numerica LoggingLevelusi come memoria, quindi presentala come LoggingLevelvalore enum.
thecoop

9
Sembra che IsDefinednon funzioni per i membri enum bitwised.
Saeed Neamati,

29

Le soluzioni di cui sopra non affrontano [Flags]situazioni.

La mia soluzione di seguito potrebbe avere alcuni problemi di prestazioni (sono sicuro che uno potrebbe ottimizzare in vari modi) ma essenzialmente dimostrerà sempre se un valore enum è valido o meno .

Si basa su tre ipotesi:

  • I valori enum in C # possono essere solo int, assolutamente nient'altro
  • I nomi di enum in C # devono iniziare con un carattere alfabetico
  • Nessun nome enum valido può essere con un segno meno: -

Chiamare ToString()un enum restituisce il intvalore se nessun enum (flag o no) è abbinato. Se viene trovato un valore enum consentito, verrà stampato il nome degli incontri.

Così:

[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"

Con queste due regole in mente possiamo supporre che se .NET Framework fa correttamente il suo lavoro, qualsiasi chiamata a un ToString()metodo enum valido si tradurrà in qualcosa che ha un carattere alfabetico come primo carattere:

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

Si potrebbe chiamarlo un "hack", ma i vantaggi sono che facendo affidamento sulla propria implementazione di Microsoft Enume sugli standard C #, non si fa affidamento sul proprio codice o controllo potenzialmente difettoso. In situazioni in cui le prestazioni non sono eccezionalmente critiche, ciò salverà molte switchdichiarazioni sgradevoli o altri controlli!

modificare

Grazie a @ChaseMedallion per aver sottolineato che la mia implementazione originale non supportava valori negativi. Questo è stato risolto e test forniti.

E i test per eseguirne il backup:

[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());
    }
}

1
Grazie per questo, ho avuto un problema simile con combinazioni di flag valide. In alternativa al controllo del primo carattere dell'enum, puoi anche provare a int.TryParse (enumValue.ToString ()) ... Se fallisce, hai un set valido di flag. Questo potrebbe effettivamente essere più lento della tua soluzione, però.
MadHenchbot,

Questa implementazione non riesce a convalidare correttamente i valori negativi, poiché il controllo è per caratteri non numerici
ChaseMedallion

Buona pesca!! Aggiornerò la mia risposta per soddisfare tale, grazie @ChaseMedallion
joshcomley

Mi piace al meglio questa soluzione, i trucchi matematici presentati funzionano solo se [Flags]hanno valori interi sensibili.
MrLore,

17

La risposta canonica sarebbe Enum.IsDefined, ma che è a: un po 'lento se usato in un ciclo stretto, e b: non utile per gli [Flags]enum.

Personalmente, smetterei di preoccuparmene, e in modo switchappropriato, ricordando:

  • se è OK non riconoscere tutto (e semplicemente non fare nulla), allora non aggiungere un default:(o avere un vuoto che default:spiega perché)
  • se esiste un comportamento predefinito ragionevole, inseriscilo in default:
  • altrimenti, gestisci quelli che conosci e lancia un'eccezione per il resto:

Così:

switch(someflag) {
    case TriBool.Yes:
        DoSomething();
        break;
    case TriBool.No:
        DoSomethingElse();
        break;
    case TriBool.FileNotFound:
        DoSomethingOther();
        break;
    default:
        throw new ArgumentOutOfRangeException("someflag");
}

non avere familiarità con gli enumer [Flags] e le prestazioni non è un problema, quindi la tua risposta sembra il motivo per cui gli enum sono stati inventati in primo luogo;) guardando i tuoi "punti" o come si chiamano, quindi devi avere un punto lì . Scommetto che non li hai ottenuti per niente, ma pensa alla situazione della lettura del file di configurazione in cui ci sono 257 valori in una sola enum defition. Per non parlare di dozzine di altri enum. Ci sarebbero molte file di casi ...
char m

@matti - sembra un esempio estremo; la deserializzazione è comunque un'area specialistica: la maggior parte dei motori di serializzazione offre gratuitamente la validazione enum.
Marc Gravell

@matti: su una nota a margine; Direi di trattare le risposte in base ai loro meriti individuali. A volte mi sbaglio completamente e qualcuno con "rep 17" potrebbe ugualmente dare una risposta perfetta .
Marc Gravell

La risposta dell'interruttore è veloce, ma non è generica.
Eldritch Conundrum,

8

Uso:

Enum.IsDefined ( typeof ( Enum ), EnumValue );


4

Per trattare con [Flags]te puoi anche usare questa soluzione dal Cookbook C # :

Innanzitutto, aggiungi un nuovo ALLvalore al tuo enum:

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

Quindi, controlla se il valore è in ALL:

public bool HandleFlagsEnum(Language language)
{
    if ((language & Language.All) == language)
    {
        return (true);
    }
    else
    {
        return (false);
    }
}

2

Un modo per fare sarebbe affidarsi al casting e alla conversione da enum a string. Quando si lancia int in un tipo Enum, int viene convertito in un valore enum corrispondente o l'enum risultante contiene solo int come valore se il valore enum non è definito per int.

enum NetworkStatus{
  Unknown=0,
  Active,
  Slow
}

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

Non testato per eventuali casi limite.


1

Come hanno detto gli altri, Enum.IsDefinedritorna falseanche se hai una valida combinazione di bit flag per un enum decorato con ilFlagsAttribute .

Purtroppo, l'unico modo per creare un metodo che ritorna vero per bit flag validi è un po 'lungo:

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;
}

Potresti voler memorizzare nella cache i risultati di GetCustomAttributein un dizionario:

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;
}

Si noti che il codice sopra utilizza il nuovo Enumvincolo su Tcui è disponibile solo da C # 7.3. È necessario passare un object valuenelle versioni precedenti e chiamarlo GetType().

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.