Gibt es eine Möglichkeit zu überprüfen, ob int in C # eine legale Aufzählung ist?

167

Ich habe ein paar SO-Beiträge gelesen und es scheint, dass die grundlegendste Operation fehlt.

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

Dies verursacht keine Ausnahmen, es ist gerne zu speichern 78. Gibt es eine Möglichkeit, einen Wert zu validieren, der in eine Aufzählung eingeht?

Charme
quelle
2
Mögliches Duplikat von Validate Enum Values
Erik

Antworten:

271

Schauen Sie sich Enum.IsDefined an

Verwendung:

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

Dies ist das Beispiel von dieser Seite:

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

Das Beispiel zeigt die folgende Ausgabe:

//       1: True
//       64: False
//       Rodent: True
//       Dog: True
//       Dog, Cat: False
//       None: True
//       NONE: False
//       9: False
//       Dog, Bird: False
SwDevMan81
quelle
@matti: Konvertiere "78" in eine beliebige Zahlendarstellung, LoggingLeveldie als Speicher verwendet wird, und präsentiere diese dann als LoggingLevelAufzählungswert.
Thecoop
9
Scheint IsDefinednicht für bitwised Enum-Mitglieder zu funktionieren.
Saeed Neamati
29

Die obigen Lösungen behandeln keine [Flags]Situationen.

Meine unten stehende Lösung kann einige Leistungsprobleme aufweisen (ich bin sicher, dass man sie auf verschiedene Arten optimieren kann), aber im Wesentlichen wird immer bewiesen, ob ein Aufzählungswert gültig ist oder nicht .

Es beruht auf drei Annahmen:

  • Aufzählungswerte in C # dürfen nur sein int, absolut nichts anderes
  • Aufzählungsnamen in C # müssen mit einem alphabetischen Zeichen beginnen
  • Kein gültiger Aufzählungsname kann mit einem Minuszeichen versehen sein: -

Aufruf ToString()auf einer ENUM gibt entweder den intWert , wenn kein ENUM (Flag oder nicht) abgestimmt ist. Wenn ein zulässiger Aufzählungswert übereinstimmt, wird der Name der Übereinstimmung (en) gedruckt.

So:

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

Unter Berücksichtigung dieser beiden Regeln können wir davon ausgehen, dass bei korrekter Ausführung von .NET Framework alle Aufrufe einer gültigen Enum- ToString()Methode zu etwas führen, dessen erstes Zeichen ein alphabetisches Zeichen ist:

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

Man könnte es als "Hack" bezeichnen, aber die Vorteile sind, dass EnumSie sich nicht auf Ihren eigenen potenziell fehlerhaften Code oder Ihre eigenen Überprüfungen verlassen, wenn Sie sich auf Microsofts eigene Implementierung von und C # -Standards verlassen. In Situationen, in denen die Leistung nicht außergewöhnlich kritisch ist, werden dadurch viele böse switchAussagen oder andere Überprüfungen eingespart!

Bearbeiten

Vielen Dank an @ChaseMedallion für den Hinweis, dass meine ursprüngliche Implementierung keine negativen Werte unterstützt. Dies wurde behoben und Tests bereitgestellt.

Und die Tests, um es zu sichern:

[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
quelle
1
Vielen Dank dafür, ich hatte ein ähnliches Problem mit gültigen Flaggenkombinationen. Alternativ zum Überprüfen des ersten Zeichens der Aufzählung können Sie auch versuchen, int.TryParse (enumValue.ToString ()) ... Wenn dies fehlschlägt, verfügen Sie über einen gültigen Satz von Flags. Dies ist jedoch möglicherweise langsamer als Ihre Lösung.
MadHenchbot
Diese Implementierung kann negative Werte nicht korrekt validieren, da auf nichtstellige Zeichen
geprüft wird
Guter Fang!! Ich werde meine Antwort aktualisieren, um solche zu berücksichtigen, danke @ChaseMedallion
Joshcomley
Ich mag diese Lösung am besten, die vorgestellten Mathe-Tricks funktionieren nur, wenn [Flags]sie vernünftige ganzzahlige Werte haben.
MrLore
17

Die kanonische Antwort wäre Enum.IsDefined, aber das ist a: ein bisschen langsam, wenn es in einer engen Schleife verwendet wird, und b: nicht nützlich für [Flags]Aufzählungen.

Persönlich würde ich aufhören, mir darüber Sorgen zu machen, und switchmich nur angemessen daran erinnern:

  • Wenn es in Ordnung ist, nicht alles zu erkennen (und einfach nichts zu tun), fügen Sie kein hinzu default:(oder lassen Sie eine leere default:Erklärung, warum).
  • Wenn es ein vernünftiges Standardverhalten gibt, geben Sie dies in das Feld ein default:
  • Andernfalls behandeln Sie die, die Sie kennen, und lösen für den Rest eine Ausnahme aus:

Wie so:

switch(someflag) {
    case TriBool.Yes:
        DoSomething();
        break;
    case TriBool.No:
        DoSomethingElse();
        break;
    case TriBool.FileNotFound:
        DoSomethingOther();
        break;
    default:
        throw new ArgumentOutOfRangeException("someflag");
}
Marc Gravell
quelle
Nicht vertraut mit [Flags] -Aufzählungen und Leistung ist kein Problem, daher scheint Ihre Antwort der Grund zu sein, warum Aufzählungen überhaupt erfunden wurden;) Betrachten Sie Ihre "Punkte" oder wie auch immer sie genannt werden, damit Sie dort einen Punkt haben müssen . Ich wette, Sie haben sie nicht umsonst bekommen, aber denken Sie an die Situation beim Lesen der Konfigurationsdatei, in der 257 Werte in einer Aufzählung enthalten sind. Geschweige denn Dutzende anderer Aufzählungen. Es würde viele Fallreihen geben ...
char m
@matti - das klingt nach einem extremen Beispiel; Die Deserialisierung ist ohnehin ein Spezialgebiet - die meisten Serialisierungs-Engines bieten eine kostenlose Enum-Validierung an.
Marc Gravell
@matti - eine Randnotiz; Ich würde sagen, behandeln Sie Antworten basierend auf ihren individuellen Verdiensten. Ich verstehe manchmal etwas völlig falsch, und jemand mit "rep 17" könnte genauso gut eine perfekte Antwort geben.
Marc Gravell
Die Antwort auf den Wechsel ist schnell, aber nicht generisch.
Eldritch Conundrum
8

Verwenden:

Enum.IsDefined ( typeof ( Enum ), EnumValue );
n535
quelle
4

Um damit umzugehen [Flags], können Sie auch diese Lösung aus dem C # -Kochbuch verwenden :

ALLFügen Sie Ihrer Aufzählung zunächst einen neuen Wert hinzu:

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

Überprüfen Sie dann, ob der Wert in ALL:

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

Eine Möglichkeit wäre, sich auf Casting und Enum-to-String-Konvertierung zu verlassen. Beim Umwandeln von int in einen Aufzählungstyp wird das int entweder in einen entsprechenden Aufzählungswert konvertiert oder die resultierende Aufzählung enthält nur int als Wert, wenn für das int kein Aufzählungswert definiert ist.

enum NetworkStatus{
  Unknown=0,
  Active,
  Slow
}

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

Nicht für Randfälle getestet.

maulik13
quelle
1

Wie die anderen sagten, wird Enum.IsDefinedzurückgegeben, falseauch wenn Sie eine gültige Kombination von Bit-Flags für eine Aufzählung haben, die mit dem verziert ist FlagsAttribute.

Leider ist die einzige Möglichkeit, eine Methode zu erstellen, die true für gültige Bitflags zurückgibt, etwas langwierig:

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

Möglicherweise möchten Sie die Ergebnisse von GetCustomAttributein einem Wörterbuch zwischenspeichern:

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

Beachten Sie, dass der obige Code die neue EnumEinschränkung verwendet, Tdie nur seit C # 7.3 verfügbar ist. Sie müssen ein object valuein älteren Versionen übergeben und es aufrufen GetType().

Strahl
quelle