Frage

Ich muss eine Ganzzahl validieren, um zu wissen, ob es sich um einen gültigen Enumerationswert handelt.

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

War es hilfreich?

Lösung

Sie müssen diese Leute lieben, die davon ausgehen, dass Daten nicht nur immer von einer Benutzeroberfläche stammen, sondern von einer Benutzeroberfläche, die unter Ihrer Kontrolle steht!

IsDefined ist für die meisten Szenarien in Ordnung, Sie könnten 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 lassen Sie „this“ einfach weg, wenn Sie denken, dass es keine geeignete int-Erweiterung ist)

Andere Tipps

Meiner Meinung nach ist der als Antwort markierte Beitrag falsch.
Die Parameter- und Datenvalidierung gehört zu den Dingen, die mir schon vor Jahrzehnten beigebracht wurden.

WARUM

Eine Validierung ist erforderlich, da einer Aufzählung grundsätzlich jeder ganzzahlige Wert zugewiesen werden kann, ohne dass ein Fehler auftritt.
Ich habe viele Tage damit verbracht, die C#-Enum-Validierung zu recherchieren, da es sich in vielen Fällen um eine notwendige Funktion handelt.

WO

Der Hauptzweck der Enum-Validierung besteht für mich darin, aus einer Datei gelesene Daten zu validieren:Man weiß nie, ob die Datei beschädigt, extern verändert oder absichtlich gehackt wurde.
Und mit Enum-Validierung der aus der Zwischenablage eingefügten Anwendungsdaten:Sie wissen nie, ob der Benutzer den Inhalt der Zwischenablage bearbeitet hat.

Allerdings habe ich Tage damit verbracht, viele Methoden zu recherchieren und zu testen, einschließlich der Leistungsprofilierung jeder Methode, die ich finden oder entwerfen konnte.

Das Ausführen von Aufrufen in System.Enum ist so langsam, dass es bei Funktionen, die Hunderte oder Tausende von Objekten enthielten, die eine oder mehrere Aufzählungen in ihren Eigenschaften hatten, die auf Grenzen überprüft werden mussten, zu spürbaren Leistungseinbußen führte.

Fazit: Finger weg von alles in der System.Enum-Klasse beim Validieren von Enum-Werten ist es furchtbar langsam.

ERGEBNIS

Die Methode, die ich derzeit zur Enum-Validierung verwende, wird wahrscheinlich viele Programmierer hier verärgern, aber sie ist meiner Meinung nach das geringste Übel für mein spezifisches Anwendungsdesign.

Ich definiere eine oder zwei Konstanten, die die Ober- und (optional) Untergrenzen der Aufzählung darstellen, und verwende sie in einem Paar if()-Anweisungen zur Validierung.
Ein Nachteil besteht darin, dass Sie die Konstanten unbedingt aktualisieren müssen, wenn Sie die Enumeration ändern.
Diese Methode funktioniert auch nur, wenn es sich bei der Aufzählung um einen „Auto“-Stil handelt, bei dem jedes Aufzählungselement ein inkrementeller ganzzahliger Wert ist, z. B. 0,1,2,3,4, ....Es funktioniert nicht ordnungsgemäß mit Flags oder Enumerationen, deren Werte nicht inkrementell sind.

Beachten Sie auch, dass diese Methode auf regulären int32s (die bei meinen Tests 38.000 Ticks erzielten) fast so schnell ist wie die normale if „<“ „>“-Methode.

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

LEISTUNG

Für diejenigen, die interessiert sind, habe ich die folgenden Variationen einer Enum-Validierung vorgestellt, und hier sind die Ergebnisse.

Die Profilerstellung wurde bei der Release-Kompilierung in einer Schleife von einer Million Mal für jede Methode mit einem zufälligen ganzzahligen Eingabewert durchgeführt.Jeder Test wurde mehr als zehnmal durchgeführt und der Durchschnitt ermittelt.Die Tick-Ergebnisse umfassen die Gesamtausführungszeit, einschließlich der Zufallszahlengenerierung usw.aber diese werden über die Tests hinweg konstant sein.1 Tick = 10 ns.

Beachten Sie, dass der Code hier nicht der vollständige Testcode ist, sondern nur die grundlegende Enumerationsvalidierungsmethode.Darüber hinaus wurden viele weitere Varianten getestet, und alle erzielten ähnliche Ergebnisse wie die hier gezeigten, bei denen 1.800.000 Ticks getestet wurden.

Vom langsamsten zum schnellsten mit gerundeten Ergebnissen aufgelistet, hoffentlich keine Tippfehler.

In der Methode festgelegte Grenzen = 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
Notiz:Diese Codeversion beschränkt sich nicht auf Min/Max, sondern gibt den Standardwert zurück, wenn sie außerhalb der Grenzen liegt.

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

System.Enum Konvertieren Sie Int32 mit Umwandlungen = 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() Min/Max-Konstanten = 43.000 Ticks = der Gewinner ist 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-

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

Bei mehreren Vergleichen besteht eine schnellere Methode darin, die Werte zunächst in a einzugeben HashSet.Dann einfach nutzen Contains um zu prü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
}

Brad Abrams warnt ausdrücklich davor Enum.IsDefined in seinem Beitrag Die Gefahr einer übermäßigen Vereinfachung.

Der beste Weg, diese Anforderung (d. h. die Notwendigkeit, Aufzählungen zu validieren) loszuwerden, besteht darin, Möglichkeiten zu entfernen, bei denen Benutzer etwas falsch machen können, z. B. eine Art Eingabefeld.Verwenden Sie beispielsweise Aufzählungen mit Dropdown-Listen, um nur gültige Aufzählungen zu erzwingen.

Diese Antwort ist eine Antwort auf die Antwort von deegee, die die Leistungsprobleme von System.Enum aufwirft, und sollte daher nicht als meine bevorzugte generische Antwort angesehen werden, sondern sich eher mit der Enum-Validierung in Szenarien mit enger Leistung befassen.

Wenn Sie ein geschäftskritisches Leistungsproblem haben, bei dem langsamer, aber funktionsfähiger Code in einer engen Schleife ausgeführt wird, würde ich persönlich versuchen, diesen Code nach Möglichkeit aus der Schleife zu entfernen, anstatt ihn durch Reduzierung der Funktionalität zu lösen.Den Code so einzuschränken, dass er nur zusammenhängende Aufzählungen unterstützt, könnte ein Albtraum sein, um einen Fehler zu finden, wenn sich beispielsweise jemand in der Zukunft dazu entschließt, einige Aufzählungswerte zu verwerfen.Vereinfacht gesagt könnten Sie Enum.GetValues ​​einfach einmal gleich zu Beginn aufrufen, um zu vermeiden, dass die gesamte Reflexion usw. tausende Male ausgelöst wird.Das sollte zu einer sofortigen Leistungssteigerung führen.Wenn Sie mehr Leistung benötigen und wissen, dass viele Ihrer Aufzählungen zusammenhängend sind (aber Sie dennoch „lückenhafte“ Aufzählungen unterstützen möchten), können Sie noch einen Schritt weiter gehen und Folgendes tun:

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 so etwas wird:

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

Ich bin mir sicher, dass die EnumValidator-Klassen effizienter geschrieben werden könnten (es ist nur ein kurzer Trick zur Demonstration), aber ganz ehrlich, wen interessiert es, was außerhalb der Importschleife passiert?Das einzige Bit, das superschnell sein muss, liegt innerhalb der Schleife.Aus diesem Grund wurde die abstrakte Klassenroute gewählt, um ein unnötiges if-enumContiguous-then-else in der Schleife zu vermeiden (die Factory Create erledigt dies im Wesentlichen im Voraus).Sie werden ein wenig Heuchelei bemerken, denn der Kürze halber beschränkt dieser Code die Funktionalität auf Int-Enums.Ich sollte IConvertible verwenden, anstatt ints direkt zu verwenden, aber diese Antwort ist bereits wortreich genug!

So mache ich es basierend auf mehreren Online-Beiträgen.Der Grund hierfür besteht darin, sicherzustellen, dass Enumerationen mit gekennzeichnet sind Flags Das Attribut kann ebenfalls erfolgreich validiert werden.

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

ich habe das gefunden Verknüpfung das beantwortet es ganz gut.Es benutzt:

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

Ich habe mich für diese Lösung aufgrund einer Mischung aus Leistung, Komfort und Wartbarkeit entschieden.

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

Und verwenden Sie es so:

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!

In meinem Fall würde der Aufruf von „Enum.IsDefined“ nur zur Hälfte reichen, da ich normalerweise den Enum-Wert „Unbekannt“ ablehnen möchte.

Dieser Ansatz vermeidet die Leistungsprobleme von Enum.IsDefined und definiert die Validierungsregel nahe der Aufzählung, was mir gefällt.

Die Erweiterungsmethode lässt sich leicht an sich ändernde Anforderungen anpassen und kann bei Bedarf auf int angewendet werden (d. h. public static bool IsValidDogBreed(this int breed))

Hier ist eine schnelle generische Lösung, die einen statischen Aufbau verwendet HashSet<T>.

Sie können dies einmal in Ihrer Toolbox definieren und es dann für Ihre gesamte Enum-Validierung verwenden.

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

Beachten Sie, dass dieser Ansatz problemlos auch auf das Parsen von Enums ausgeweitet werden kann, indem ein Wörterbuch mit Zeichenfolgenschlüsseln verwendet wird (wobei Groß- und Kleinschreibung und numerische Zeichenfolgendarstellungen berücksichtigt werden).

Um zu überprüfen, ob ein Wert ein gültiger Wert in einer Aufzählung ist, müssen Sie nur die statische Methode aufrufen 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
}
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top