2008-08-17 15 views
51

Ich muss eine Ganzzahl validieren, um zu wissen, ob es sich um einen gültigen Enum-Wert handelt.Validiere Enum-Werte

Was ist der beste Weg, dies in C# zu tun?

+1

Für einen Ansatz mit Fahnen es nützlich sein könnte, diese Antwort auf eine doppelte Frage zur Kasse: http://stackoverflow.com/a/23177585/5190842 – Erik

+0

prüfen 'EnumDataTypeAttribute' – Timeless

Antwort

61

Du hast diese Leute zu lieben, die davon ausgehen, dass die Daten nicht nur immer von einem UI kommen, sondern eine Benutzeroberfläche in Ihrer Kontrolle!

IsDefined ist gut für die meisten Szenarien, können Sie beginnen mit:

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

(Natürlich nur die ‚this‘ fallen, wenn Sie nicht denken, es ist eine geeignete int-Erweiterung)

+4

Bitte beachten Sie auch, dass Enum.IsDefined verwendet Reflexion, die zu Leistungsproblemen führen könnte – Guldan

+0

Das ist bereits in anderen Antworten bemerkt und angesprochen. – Vman

9

Brad Abrams warnt ausdrücklich vor Enum.IsDefined in seinem Beitrag The Danger of Oversimplification.

Der beste Weg, um diese Anforderung loszuwerden (d. H. Die Notwendigkeit, Enums zu validieren), besteht darin, Wege zu entfernen, auf denen Benutzer Fehler machen können, z. B. ein Eingabefeld. Verwenden Sie Enums mit Dropdown-Listen, um beispielsweise nur gültige Enums zu erzwingen.

+5

Meine Eingabe aus einer XML-Datei kommt produziert von einem von vielen möglichen Programmen, die ich nicht kontrolliere, wo die Qualität der Daten stark variiert. Ist Enum.IsDefined wirklich so schlecht, weil es für mich in dieser Situation am besten scheint? –

+0

@Richard Wie bei allem im Leben gibt es spezielle Fälle, in denen eine schlechte Idee die richtige Lösung für diese Situation ist. Wenn Sie denken, dass dies die beste Lösung für Ihren Fall ist, dann machen Sie weiter. :) Auch Singletons und globale Variablen und verschachtelte ifs sind eine gute Idee für bestimmte Situationen ... –

+6

Ihr Ansatz (Einschränkung, was in der Benutzeroberfläche verfügbar ist) hat die gleichen Nachteile wie "Enum.IsDefined": Wenn der UI-Code nicht synchron ist Mit der Enumeration können Sie Werte außerhalb des Bereichs erhalten (dh nicht in der Enumeration deklariert). Obwohl man mit diesen Werten immer noch umgehen muss (wie zum Beispiel eine "default" Groß-/Kleinschreibung in "switch" -Anweisungen), scheint es eine gute Übung zu sein, "Enum.IsDefined" zu verwenden, bevor ein Wert in einem Objektfeld gespeichert wird früh gefangen, anstatt es herumschweben zu lassen, bis nicht mehr klar ist, wo der falsche Wert herkommt. –

1

Ich fand diese link, die es recht gut beantwortet. Es verwendet:

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

Wird dies nicht nur eine Ausnahme auslösen, wenn eine nicht enumerale Ganzzahl an sie übergeben wird? –

+3

@Richard - ja, es wird ...seit 3 ​​Jahren, seit ich dies geschrieben habe und der Himmel mir helfen, wenn ich weiß, was ich dachte, anders als - wenn es wirft, ist es nicht –

+0

Werfen einer Ausnahme hat auch Leistungseinbußen. Sie sollten niemals eine Exception werfen, wenn es eine bessere Lösung gibt. – Kody

16

IMHO die Post markiert als die Antwort ist falsch.
Die Parameter- und Datenvalidierung ist eines der Dinge, die mir vor Jahrzehnten aufgedrängt wurden.

WARUM

Validierung erforderlich ist, da im wesentlichen kann jeder ganzzahlige Wert ein ENUM zugewiesen werden, ohne einen Fehler zu werfen.
Ich verbrachte viele Tage damit, C# enum-Validierung zu erforschen, weil es in vielen Fällen eine notwendige Funktion ist.

WHERE

Der Hauptzweck für mich in Enum-Validierung ist aus einer Datei gelesenen Daten bei der Validierung: Sie nie wissen, ob die Datei beschädigt ist, oder wurde von außen verändert oder wurde absichtlich gehackt.
Und mit enum Validierung von Anwendungsdaten aus der Zwischenablage eingefügt: Sie wissen nie, ob der Benutzer den Inhalt der Zwischenablage bearbeitet hat.

Das sagte, verbrachte ich Tage erforschte und testete viele Methoden einschließlich Profiling die Leistung von jeder Methode, die ich finden oder entwerfen konnte.

Anrufe in irgendetwas in System.Enum sind so langsam, dass es eine merkliche Leistungseinbuße für Funktionen war, die Hunderte oder Tausende von Objekten enthielten, die ein oder mehrere Enums in ihren Eigenschaften hatten, die für Grenzen validiert werden mussten.

Unterm Strich, bleiben Sie weg von alles in der Klasse System.Enum beim Validieren Enum-Werte, es ist schrecklich langsam.

RESULT

Die Methode, die ich zur Zeit für ENUM Validierung verwende wahrscheinlich hier rollen die Augen von vielen Programmierern zeichnen, aber es ist imho das kleinste Übel für mein spezifisches Anwendungsdesign.

Ich definiere eine oder zwei Konstanten, die die oberen und (optional) unteren Grenzen der Enumeration sind, und verwende sie in einem Paar von if() -Anweisungen zur Validierung.
Ein Nachteil ist, dass Sie die Konstanten aktualisieren müssen, wenn Sie die Enumeration ändern.
Diese Methode funktioniert auch nur, wenn die Enumeration ein "Auto" -Stil ist, wobei jedes Enum-Element ein inkrementeller Integer-Wert wie 0,1,2,3,4, ... ist. Es funktioniert nicht ordnungsgemäß mit Flags oder Aufzählungen mit Werten, die nicht inkrementell sind.

Beachten Sie auch, dass diese Methode fast so schnell wie regulär ist, wenn "<" ">" auf regulären Int32s (die 38.000 Ticks bei meinen Tests erzielte).

Zum Beispiel:

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

Für diejenigen, die interessiert sind, habe ich die folgenden Variationen über einen ENUM Validierung profiliert, und hier sind die Ergebnisse.

Die Profilerstellung wurde beim Release-Compile in einer millionenfachen Schleife für jede Methode mit einem zufälligen ganzzahligen Eingabewert durchgeführt. Jeder Test wurde mehr als 10 Mal durchgeführt und gemittelt. Die Tick-Ergebnisse beinhalten die Gesamtzeit, die ausgeführt werden soll, einschließlich der Zufallszahlengenerierung usw., aber diese sind während der Tests konstant. 1 Tick = 10ns.

Beachten Sie, dass der Code hier nicht der vollständige Testcode ist, sondern nur die grundlegende Enum-Validierungsmethode. Es gab auch viele zusätzliche Variationen, die getestet wurden, und alle mit ähnlichen Ergebnissen, wie hier gezeigt, mit 1.800.000 Ticks.

Listed am langsamsten zu am schnellsten mit abgerundeten Ergebnissen, hoffentlich keine Tippfehler.

Bounds in Methode bestimmt = 13600000 Zecken

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 Zecken
Hinweis: Diese Codeversion klemmt nicht zu Min/Max kehrt aber Standard außerhalb der Grenzen, wenn .

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

System.Enum Konvertieren Int32 mit Abgüssen = 1.800.000 Zecken

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() Min/Max-Konstanten = 43.000 Ticks = der Gewinner von 42x und 316x schneller.

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

-eol-

+6

Eine große Anzahl unserer Enums sind nicht zusammenhängend, also keine Option in vielen Szenarien. 'Enum.IsDefined (typeof (T)') wird in Ihrem Testszenario langsam sein, da .net viel Reflektion, Boxen usw. ausführt. Das Aufrufen jedes Mal, wenn Sie eine Zeile in Ihrer Importdatei parsen, wird niemals schnell sein Leistung ist der Schlüssel, dann würde ich Enum.GetValues ​​einmal am Anfang des Imports aufrufen.Es wird nie so schnell ein einfacher <> Vergleich sein, aber Sie wissen, dass es für alle Enums funktioniert.Alternativ könnten Sie einen intelligenteren Enum-Parser haben Ich gebe eine andere Antwort, da es nicht den Platz gibt, sauber zu antworten! – Vman

+0

Was ist, wenn Ihre Enumeration Werte überspringt? –

+0

@johnny 5 - Aus den obigen Informationen: Diese Methode funktioniert auch nur, wenn die Enum ein "Auto" -Stil ist Jedes Enumerationselement ist ein inkrementeller Ganzzahlwert wie 0,1,2,3,4 usw. Es funktioniert nicht ordnungsgemäß mit Flags oder Enums, die Werte haben, die nicht inkrementell sind. – deegee

2

Dies ist, wie ich es tun Online mehrere Beiträge basiert. Der Grund dafür ist, dass Enums, die mit dem Attribut Flags markiert sind, ebenfalls erfolgreich validiert werden können.

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

Diese Antwort ist eine Antwort Antwort auf deegee ist, welche die Leistungsprobleme von System.Enum wirft so sollte nicht als meine bevorzugte generische Antwort genommen werden, mehr Adressierung ENUM Validierung in engen Performance-Szenarien.

Wenn Sie ein missionskritisches Leistungsproblem haben, bei dem langsamer aber funktionaler Code in einer engen Schleife ausgeführt wird, würde ich persönlich versuchen, diesen Code aus der Schleife zu entfernen, anstatt ihn durch Reduzieren der Funktionalität zu lösen. Wenn der Code so eingeschränkt wird, dass er nur zusammenhängende Aufzählungen unterstützt, kann es ein Albtraum sein, einen Fehler zu finden, wenn sich beispielsweise jemand in der Zukunft dazu entscheidet, einige Aufzählungswerte abzulehnen. Vereinfacht gesagt könnte man Enum.GetValues ​​einfach gleich am Anfang aufrufen, um zu vermeiden, dass alle Reflexionen usw. tausende Male ausgelöst werden. Das sollte dir eine sofortige Leistungssteigerung bringen. Wenn Sie mehr Leistung benötigen und Sie wissen, dass viele Ihrer Aufzählungen angrenzen (aber Sie wollen immer noch ‚lückenhaft‘ Aufzählungen unterstützen) Sie einen Schritt weiter gehen und so etwas wie:

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

Wo Ihre Schleife wird so etwas wie:

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

ich bin sicher, dass die EnumValidator Klassen effizienter geschrieben konnte (es ist nur eine schnelle Hack zeigen), aber ehrlich gesagt, wer sich interessiert, was außerhalb der Import Schleife geschieht? Das einzige Bit, das superschnell sein muss, ist innerhalb der Schleife. Dies war der Grund dafür, die abstrakte Klassenroute zu nehmen, um ein unnötiges if-enumContiguous-then-else in der Schleife zu vermeiden (das Factory-Create macht dies im Wesentlichen im Voraus). Sie werden ein wenig Heuchelei bemerken, der Kürze wegen beschränkt dieser Code die Funktionalität auf int-enums. Ich sollte IConvertible verwenden, anstatt Int direkt zu verwenden, aber diese Antwort ist schon wortreich!

4

Wie andere erwähnt haben, ist Enum.IsDefined langsam, etwas, das Sie beachten müssen, wenn es in einer Schleife ist.

Bei mehreren Vergleichen ist es eine schnellere Methode, zuerst die Werte in eine HashSet einzutragen. Dann einfach Contains verwenden um zu überprüfen, ob der Wert gültig ist, etwa so:

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 
} 
+0

Eine andere Annahme dieser Antwort wäre ein 'HashSet ', das die Notwendigkeit der Umwandlung in 'int' überflüssig machen würde, wenn Sie eine' enum' anstelle einer 'int' validieren. Der Code zum Einrichten würde folgendermaßen aussehen: 'var valueVals = new HashSet (Enum.GetValues ​​(typeof (MyEnum)). Cast ());'. Die Verwendung wäre natürlich die gleiche. – Erik

0

Um zu prüfen ob ein Wert ein gültiger Wert in einer Aufzählung ist, brauchen Sie nur Enum.IsDefined die statische Methode aufrufen.

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 
} 
+0

Downvoted nicht für falsch, sondern für das Schreiben einer Antwort 8 Jahre zu spät, die keine neuen Informationen, die nicht bereits in der angenommenen Antwort bietet. –