Wie TryParse für Enum-Wert?


94

Ich möchte eine Funktion schreiben, die einen bestimmten Wert (als Zeichenfolge übergeben) anhand möglicher Werte von a validieren kann enum. Im Falle einer Übereinstimmung sollte die Aufzählungsinstanz zurückgegeben werden. Andernfalls sollte ein Standardwert zurückgegeben werden.

Die Funktion verwendet try/ möglicherweise nicht intern catch, was die Verwendung ausschließt Enum.Parse, wodurch eine Ausnahme ausgelöst wird , wenn ein ungültiges Argument angegeben wird.

Ich möchte etwas in der Art einer TryParseFunktion verwenden, um dies zu implementieren:

public static TEnum ToEnum<TEnum>(this string strEnumValue, TEnum defaultValue)
{
   object enumValue;
   if (!TryParse (typeof (TEnum), strEnumValue, out enumValue))
   {
       return defaultValue;
   }
   return (TEnum) enumValue;
}

8
Ich verstehe diese Frage nicht. Sie sagen: "Ich möchte dieses Problem lösen, aber ich möchte keine der Methoden verwenden, die mir eine Lösung geben würden." Was ist der Punkt?
Domenic

1
Was ist Ihre Abneigung gegen Try / Catch-Lösung? Wenn Sie versuchen, Ausnahmen zu vermeiden, weil sie "teuer" sind, geben Sie sich bitte eine Pause. In 99% der Fälle ist die Kostenausnahme für das Werfen / Fangen im Vergleich zu Ihrem Hauptcode vernachlässigbar.
SolutionYogi

1
Die Kosten für die Ausnahmebehandlung sind nicht so hoch. Zur Hölle, die internen Implementierungen all dieser Aufzählungskonvertierungen sind voll von Ausnahmebehandlung. Ich mag es wirklich nicht, wenn Ausnahmen während der normalen Anwendungslogik ausgelöst und abgefangen werden. Es kann manchmal nützlich sein, alle Ausnahmen zu unterbrechen, die ausgelöst werden (selbst wenn sie abgefangen werden). Das Auslösen von Ausnahmen überall macht die Verwendung viel ärgerlicher :)
Thorarin

3
@Domenic: Ich suche nur nach einer besseren Lösung als das, was ich bereits weiß. Würden Sie jemals zu einer Eisenbahnanfrage gehen, um nach einer Route oder einem Zug zu fragen, die Sie bereits kennen :).
Manish Basantani

2
@Amby, die Kosten für die einfache Eingabe eines Try / Catch-Blocks sind vernachlässigbar. Die Kosten für das Auslösen einer Ausnahme sind nicht, aber das sollte dann außergewöhnlich sein, oder? Sagen Sie auch nicht "wir wissen es nie" ... profilieren Sie den Code und finden Sie es heraus. Verschwenden Sie keine Zeit damit, sich zu fragen, ob etwas langsam ist.
Akmad

Antworten:


31

Wie andere gesagt haben, müssen Sie Ihre eigenen implementieren TryParse. Simon Mourier bietet eine vollständige Implementierung, die sich um alles kümmert.

Wenn Sie Bitfeld-Aufzählungen (dh Flags) verwenden, müssen Sie auch eine Zeichenfolge behandeln, bei "MyEnum.Val1|MyEnum.Val2"der es sich um eine Kombination aus zwei Aufzählungswerten handelt. Wenn Sie nur Enum.IsDefinedmit dieser Zeichenfolge aufrufen , wird false zurückgegeben, obwohl Enum.Parsedies korrekt behandelt wird.

Aktualisieren

Wie von Lisa und Christian in den Kommentaren erwähnt, Enum.TryParseist es jetzt für C # in .NET4 und höher verfügbar.

MSDN Docs


Vielleicht am wenigsten sexy, aber ich stimme zu, dass dies definitiv das Beste ist, bis Ihr Code auf .NET 4 migriert wird.
Lisa

1
Wie unten erwähnt, aber nicht wirklich sichtbar: Ab .Net 4 ist Enum.TryParse verfügbar und funktioniert ohne zusätzliche Codierung. Weitere Informationen erhalten Sie von MSDN: msdn.microsoft.com/library/vstudio/dd991317%28v=vs.100%29.aspx
Christian

106

Enum.IsDefined erledigt alles. Es ist möglicherweise nicht so effizient wie ein TryParse, funktioniert aber ohne Ausnahmebehandlung.

public static TEnum ToEnum<TEnum>(this string strEnumValue, TEnum defaultValue)
{
    if (!Enum.IsDefined(typeof(TEnum), strEnumValue))
        return defaultValue;

    return (TEnum)Enum.Parse(typeof(TEnum), strEnumValue);
}

Bemerkenswert: TryParseIn .NET 4.0 wurde eine Methode hinzugefügt.


1
Beste Antwort, die ich bisher gesehen habe ... kein Versuch / Fang, keine GetNames :)
Thomas Levesque


6
Auch gibt es keinen Ignorierfall auf IsDefined
Anthony Johnston

2
@Anthony: Wenn Sie die Groß- und Kleinschreibung nicht berücksichtigen möchten, benötigen Sie GetNames. Intern verwenden alle diese Methoden (einschließlich Parse) GetHashEntrydie eigentliche Reflexion - einmal. Auf der positiven Seite, .NET 4.0 hat eine TryParse, und es ist auch generisch :)
Thorarin

+1 Es hat meinen Tag gerettet! Ich portiere eine Menge Code von .NET 4 nach .NET 3.5 und du hast mich gerettet :)
daitangio

20

Hier ist eine benutzerdefinierte Implementierung von EnumTryParse. Im Gegensatz zu anderen gängigen Implementierungen unterstützt es auch die mit dem FlagsAttribut gekennzeichnete Aufzählung .

    /// <summary>
    /// Converts the string representation of an enum to its Enum equivalent value. A return value indicates whether the operation succeeded.
    /// This method does not rely on Enum.Parse and therefore will never raise any first or second chance exception.
    /// </summary>
    /// <param name="type">The enum target type. May not be null.</param>
    /// <param name="input">The input text. May be null.</param>
    /// <param name="value">When this method returns, contains Enum equivalent value to the enum contained in input, if the conversion succeeded.</param>
    /// <returns>
    /// true if s was converted successfully; otherwise, false.
    /// </returns>
    public static bool EnumTryParse(Type type, string input, out object value)
    {
        if (type == null)
            throw new ArgumentNullException("type");

        if (!type.IsEnum)
            throw new ArgumentException(null, "type");

        if (input == null)
        {
            value = Activator.CreateInstance(type);
            return false;
        }

        input = input.Trim();
        if (input.Length == 0)
        {
            value = Activator.CreateInstance(type);
            return false;
        }

        string[] names = Enum.GetNames(type);
        if (names.Length == 0)
        {
            value = Activator.CreateInstance(type);
            return false;
        }

        Type underlyingType = Enum.GetUnderlyingType(type);
        Array values = Enum.GetValues(type);
        // some enums like System.CodeDom.MemberAttributes *are* flags but are not declared with Flags...
        if ((!type.IsDefined(typeof(FlagsAttribute), true)) && (input.IndexOfAny(_enumSeperators) < 0))
            return EnumToObject(type, underlyingType, names, values, input, out value);

        // multi value enum
        string[] tokens = input.Split(_enumSeperators, StringSplitOptions.RemoveEmptyEntries);
        if (tokens.Length == 0)
        {
            value = Activator.CreateInstance(type);
            return false;
        }

        ulong ul = 0;
        foreach (string tok in tokens)
        {
            string token = tok.Trim(); // NOTE: we don't consider empty tokens as errors
            if (token.Length == 0)
                continue;

            object tokenValue;
            if (!EnumToObject(type, underlyingType, names, values, token, out tokenValue))
            {
                value = Activator.CreateInstance(type);
                return false;
            }

            ulong tokenUl;
            switch (Convert.GetTypeCode(tokenValue))
            {
                case TypeCode.Int16:
                case TypeCode.Int32:
                case TypeCode.Int64:
                case TypeCode.SByte:
                    tokenUl = (ulong)Convert.ToInt64(tokenValue, CultureInfo.InvariantCulture);
                    break;

                //case TypeCode.Byte:
                //case TypeCode.UInt16:
                //case TypeCode.UInt32:
                //case TypeCode.UInt64:
                default:
                    tokenUl = Convert.ToUInt64(tokenValue, CultureInfo.InvariantCulture);
                    break;
            }

            ul |= tokenUl;
        }
        value = Enum.ToObject(type, ul);
        return true;
    }

    private static char[] _enumSeperators = new char[] { ',', ';', '+', '|', ' ' };

    private static object EnumToObject(Type underlyingType, string input)
    {
        if (underlyingType == typeof(int))
        {
            int s;
            if (int.TryParse(input, out s))
                return s;
        }

        if (underlyingType == typeof(uint))
        {
            uint s;
            if (uint.TryParse(input, out s))
                return s;
        }

        if (underlyingType == typeof(ulong))
        {
            ulong s;
            if (ulong.TryParse(input, out s))
                return s;
        }

        if (underlyingType == typeof(long))
        {
            long s;
            if (long.TryParse(input, out s))
                return s;
        }

        if (underlyingType == typeof(short))
        {
            short s;
            if (short.TryParse(input, out s))
                return s;
        }

        if (underlyingType == typeof(ushort))
        {
            ushort s;
            if (ushort.TryParse(input, out s))
                return s;
        }

        if (underlyingType == typeof(byte))
        {
            byte s;
            if (byte.TryParse(input, out s))
                return s;
        }

        if (underlyingType == typeof(sbyte))
        {
            sbyte s;
            if (sbyte.TryParse(input, out s))
                return s;
        }

        return null;
    }

    private static bool EnumToObject(Type type, Type underlyingType, string[] names, Array values, string input, out object value)
    {
        for (int i = 0; i < names.Length; i++)
        {
            if (string.Compare(names[i], input, StringComparison.OrdinalIgnoreCase) == 0)
            {
                value = values.GetValue(i);
                return true;
            }
        }

        if ((char.IsDigit(input[0]) || (input[0] == '-')) || (input[0] == '+'))
        {
            object obj = EnumToObject(underlyingType, input);
            if (obj == null)
            {
                value = Activator.CreateInstance(type);
                return false;
            }
            value = obj;
            return true;
        }

        value = Activator.CreateInstance(type);
        return false;
    }

1
Sie haben die beste Implementierung bereitgestellt, und ich habe sie für meine eigenen Zwecke verwendet. Ich frage mich jedoch, warum Sie Activator.CreateInstance(type)den Standard-Enum-Wert erstellen und nicht Enum.ToObject(type, 0). Nur Geschmackssache?
Pierre Arnaud

1
@Pierre - Hmmm ... nein, es schien zu dieser Zeit nur natürlicher :-) Vielleicht ist Enum.ToObject schneller, da es intern einen internen Aufruf InternalBoxEnum verwendet? Ich habe das nie überprüft ...
Simon Mourier

2
Wie unten erwähnt, aber nicht wirklich sichtbar: Ab .Net 4 ist Enum.TryParse verfügbar und funktioniert ohne zusätzliche Codierung. Weitere Informationen erhalten Sie von MSDN: msdn.microsoft.com/library/vstudio/dd991317%28v=vs.100%29.aspx
Christian

16

Am Ende müssen Sie dies umsetzen Enum.GetNames:

public bool TryParseEnum<T>(string str, bool caseSensitive, out T value) where T : struct {
    // Can't make this a type constraint...
    if (!typeof(T).IsEnum) {
        throw new ArgumentException("Type parameter must be an enum");
    }
    var names = Enum.GetNames(typeof(T));
    value = (Enum.GetValues(typeof(T)) as T[])[0];  // For want of a better default
    foreach (var name in names) {
        if (String.Equals(name, str, caseSensitive ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase)) {
            value = (T)Enum.Parse(typeof(T), name);
            return true;
        }
    }
    return false;
}

Zusätzliche Bemerkungen:

  • Enum.TryParseist in .NET 4 enthalten. Siehe hier http://msdn.microsoft.com/library/dd991876(VS.100).aspx
  • Ein anderer Ansatz wäre, Enum.Parsedie Ausnahme, die ausgelöst wird, wenn sie fehlschlägt , direkt zu verpacken . Dies kann schneller sein, wenn eine Übereinstimmung gefunden wird, wird aber wahrscheinlich langsamer, wenn nicht. Abhängig von den Daten, die Sie verarbeiten, kann dies eine Nettoverbesserung sein oder auch nicht.

EDIT: Habe gerade eine bessere Implementierung gesehen, die die notwendigen Informationen zwischenspeichert: http://damieng.com/blog/2010/10/17/enums-better-syntax-improved-performance-and-tryparse-in-net- 3-5


Ich wollte vorschlagen, Standard (T) zu verwenden, um den Standardwert festzulegen. Es stellte sich heraus, dass dies nicht für alle Aufzählungen funktionieren würde. Beispiel: Wenn der zugrunde liegende Typ für die Aufzählung int default (T) war, wird immer 0 zurückgegeben, was für die Aufzählung möglicherweise gültig ist oder nicht.
Daniel Ballinger

Die Implementierung in Damiengs Blog unterstützt keine Aufzählungen mit dem FlagsAttribut.
Uwe Keim

9

Basierend auf .NET 4.5

Beispielcode unten

using System;

enum Importance
{
    None,
    Low,
    Medium,
    Critical
}

class Program
{
    static void Main()
    {
    // The input value.
    string value = "Medium";

    // An unitialized variable.
    Importance importance;

    // Call Enum.TryParse method.
    if (Enum.TryParse(value, out importance))
    {
        // We now have an enum type.
        Console.WriteLine(importance == Importance.Medium);
    }
    }
}

Referenz: http://www.dotnetperls.com/enum-parse



4
enum EnumStatus
{
    NAO_INFORMADO = 0,
    ENCONTRADO = 1,
    BLOQUEADA_PELO_ENTREGADOR = 2,
    DISPOSITIVO_DESABILITADO = 3,
    ERRO_INTERNO = 4,
    AGARDANDO = 5
}

...

if (Enum.TryParse<EnumStatus>(item.status, out status)) {

}

2

Derzeit gibt es kein sofort einsatzbereites Enum.TryParse. Dies wurde bei Connect angefordert ( noch kein Enum.TryParse ) und erhielt eine Antwort, die auf eine mögliche Aufnahme in das nächste Framework nach .NET 3.5 hinweist. Sie müssen zunächst die vorgeschlagenen Problemumgehungen implementieren.


1

Die einzige Möglichkeit, die Ausnahmebehandlung zu vermeiden, ist die Verwendung der GetNames () -Methode. Wir alle wissen, dass Ausnahmen nicht für die allgemeine Anwendungslogik missbraucht werden sollten :)


1
Es ist nicht der einzige Weg. Enum.IsDefined (..) verhindert, dass Ausnahmen im Benutzercode ausgelöst werden.
Thorarin

1

Ist das Zwischenspeichern einer dynamisch generierten Funktion / eines Wörterbuchs zulässig?

Da Sie den Typ der Aufzählung nicht im Voraus zu kennen scheinen, könnte die erste Ausführung etwas erzeugen, das nachfolgende Ausführungen ausnutzen könnten.

Sie können sogar das Ergebnis von Enum.GetNames () zwischenspeichern

Versuchen Sie, für CPU oder Speicher zu optimieren? Müssen Sie wirklich ?


Die Idee ist, die CPU zu optimieren. Stimmen Sie zu, dass ich es im Kostenspeicher tun kann. Aber es ist nicht die Lösung, die ich suche. Vielen Dank.
Manish Basantani

0

Wie andere bereits sagten, müssen Sie IsDefined oder GetNames verwenden, wenn Sie Try & Catch nicht verwenden ... Hier sind einige Beispiele ... sie sind im Grunde alle gleich, das erste, das nullfähige Aufzählungen behandelt. Ich bevorzuge die zweite, da es sich um eine Erweiterung für Strings handelt, nicht für Enums ... aber Sie können sie mischen, wie Sie möchten!

  • www.objectreference.net/post/Enum-TryParse-Extension-Method.aspx
  • flatlinerdoa.spaces.live.com/blog/cns!17124D03A9A052B0!605.entry
  • mironabramson.com/blog/post/2008/03/Another-version-for-the-missing-method-EnumTryParse.aspx
  • lazyloading.blogspot.com/2008/04/enumtryparse-with-net-35-extension.html

0

Es gibt kein TryParse, da der Typ der Aufzählung erst zur Laufzeit bekannt ist. Ein TryParse, der der gleichen Methode wie die Date.TryParse-Methode folgt, würde einen impliziten Konvertierungsfehler für den ByRef-Parameter auslösen.

Ich schlage vor, so etwas zu tun:

//1 line call to get value
MyEnums enumValue = (Sections)EnumValue(typeof(Sections), myEnumTextValue, MyEnums.SomeEnumDefault);

//Put this somewhere where you can reuse
public static object EnumValue(System.Type enumType, string value, object NotDefinedReplacement)
{
    if (Enum.IsDefined(enumType, value)) {
        return Enum.Parse(enumType, value);
    } else {
        return Enum.Parse(enumType, NotDefinedReplacement);
    }
}

Für TryMethoden, deren Ergebnisse Werttypen sein nullkönnen oder bei denen es sich möglicherweise um legitime Ergebnisse handelt (z. B. Dictionary.TryGetValue, which has both such traits), the normal pattern is for a Try`-Methode zum Zurückgeben boolund Übergeben des Ergebnisses als outParameter). Für Methoden, die Klassentypen zurückgeben, bei denen nulles sich nicht um ein gültiges Ergebnis handelt, gibt es keine Schwierigkeiten bei der Verwendung einer nullRückgabe um Fehler anzuzeigen.
Supercat

-1

Schauen Sie sich die Enum-Klasse (struct?) Selbst an. Es gibt eine Parse-Methode, aber ich bin mir bei einer Tryparse nicht sicher.


Ich kenne die Enum.Parse-Methode (typeof (TEnum), strEnumValue). Es wird eine ArgumentException ausgelöst, wenn strEnumValue nicht gültig ist. Auf der Suche nach TryParse ........
Manish Basantani

-2

Diese Methode konvertiert einen Aufzählungstyp:

  public static TEnum ToEnum<TEnum>(object EnumValue, TEnum defaultValue)
    {
        if (!Enum.IsDefined(typeof(TEnum), EnumValue))
        {
            Type enumType = Enum.GetUnderlyingType(typeof(TEnum));
            if ( EnumValue.GetType() == enumType )
            {
                string name = Enum.GetName(typeof(HLink.ViewModels.ClaimHeaderViewModel.ClaimStatus), EnumValue);
                if( name != null)
                    return (TEnum)Enum.Parse(typeof(TEnum), name);
                return defaultValue;
            }
        }
        return (TEnum)Enum.Parse(typeof(TEnum), EnumValue.ToString());
    } 

Es überprüft den zugrunde liegenden Typ und erhält den Namen zum Parsen. Wenn alles fehlschlägt, wird der Standardwert zurückgegeben.


3
Was macht das? "Enum.GetName (typeof (HLink.ViewModels.ClaimHeaderViewModel.ClaimStatus), EnumValue)" Wahrscheinlich eine gewisse Abhängigkeit von Ihrem lokalen Code.
Manish Basantani
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.