Jak TryParse dla wartości wyliczenia?

97

Chcę napisać funkcję, która może zweryfikować podaną wartość (przekazaną jako ciąg) w porównaniu z możliwymi wartościami pliku enum. W przypadku dopasowania powinien zwrócić instancję wyliczenia; w przeciwnym razie powinien zwrócić wartość domyślną.

Funkcja nie może wewnętrznie używać try/ catch, co wyklucza używanie Enum.Parse, które zgłasza wyjątek, gdy zostanie podany nieprawidłowy argument.

Chciałbym użyć czegoś w rodzaju TryParsefunkcji, aby zaimplementować to:

public static TEnum ToEnum<TEnum>(this string strEnumValue, TEnum defaultValue)
{
   object enumValue;
   if (!TryParse (typeof (TEnum), strEnumValue, out enumValue))
   {
       return defaultValue;
   }
   return (TEnum) enumValue;
}
Manish Basantani
źródło
8
Nie rozumiem tego pytania; mówisz „Chcę rozwiązać ten problem, ale nie chcę używać żadnej z metod, które dałyby mi rozwiązanie”. Jaki jest sens?
Domenic
1
Jaka jest Twoja niechęć do wypróbowania / złapania rozwiązania? Jeśli próbujesz uniknąć wyjątków, ponieważ są one „kosztowne”, zrób sobie przerwę. W 99% przypadków wyjątek dotyczący kosztu wyrzucenia / złapania jest nieistotny w porównaniu z głównym kodem.
Rozwiązanie
4
@Domenic: Po prostu szukam lepszego rozwiązania niż to, co już wiem. Czy kiedykolwiek udałbyś się do zapytania kolejowego, aby zapytać o trasę lub pociąg, który już znasz :).
Manish Basantani
2
@Yogi, @Thorarin: spróbuj ... złapanie zawsze będzie moim ostatnim wyborem. Nigdy nie wiemy, czy są kosztowne. co, jeśli ktoś wywoła moją metodę narzędzia na liście setek pozycji?
Manish Basantani
2
@Amby, koszt prostego wprowadzenia bloku try / catch jest znikomy. Koszt WYRZUCENIA wyjątku nie jest taki, ale to ma być wyjątkowe, prawda? Nie mów też „nigdy nie wiemy” ... profiluj kod i dowiedz się. Nie trać czasu na zastanawianie się, czy coś jest powolne, DOWIEDZ SIĘ!
akmad

Odpowiedzi:

31

Jak powiedzieli inni, musisz wdrożyć własne TryParse. Simon Mourier zapewnia pełną implementację, która dba o wszystko.

Jeśli używasz wyliczeń pól bitowych (tj. Flag), musisz również obsłużyć łańcuch, taki jak "MyEnum.Val1|MyEnum.Val2"będący kombinacją dwóch wartości wyliczeniowych. Jeśli po prostu wywołasz Enum.IsDefinedten ciąg, zwróci on fałsz, mimo że Enum.Parseobsługuje go poprawnie.

Aktualizacja

Jak wspomnieli Lisa i Christian w komentarzach, Enum.TryParsejest teraz dostępny dla języka C # w .NET4 i nowszych.

Dokumenty MSDN

Victor Arndt Mueller
źródło
Być może najmniej sexy, ale zgadzam się, że jest to zdecydowanie najlepsze, dopóki twój kod nie zostanie przeniesiony do .NET 4.
Lisa
1
Jak wspomniano poniżej, ale niezbyt widoczne: od .Net 4 Enum.TryParse jest dostępne i działa bez dodatkowego kodowania. Więcej informacji można znaleźć w witrynie MSDN: msdn.microsoft.com/library/vstudio/dd991317%28v=vs.100%29.aspx
Christian
106

Enum.IsDefined załatwi sprawę. Może nie być tak wydajna, jak prawdopodobnie TryParse, ale będzie działać bez obsługi wyjątków.

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

Warto zauważyć: TryParsew .NET 4.0 dodano metodę.

Thorarin
źródło
1
Najlepsza odpowiedź, jaką do tej pory widziałem ... bez próby / złapania, bez GetNames :)
Thomas Levesque
13
Wady Enum.IsDefined: blogs.msdn.com/brada/archive/2003/11/29/50903.aspx
Nader Shirazie
6
nie ma też możliwości ignorowania sprawy na IsDefined
Anthony Johnston,
2
@Anthony: jeśli chcesz wspierać niewrażliwość na wielkość liter, potrzebujesz GetNames. Wewnętrznie wszystkie te metody (w tym Parse) używają GetHashEntry, co powoduje rzeczywistą refleksję - raz. Z drugiej strony .NET 4.0 ma TryParse i jest też ogólny :)
Thorarin
+1 Uratowało mi to dzień! Przenoszę część kodu z .NET 4 do .NET 3.5 i uratowałeś mnie :)
daitangio
20

Oto niestandardowa implementacja EnumTryParse. W przeciwieństwie do innych typowych implementacji obsługuje również wyliczenie oznaczone Flagsatrybutem.

    /// <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;
    }
Simon Mourier
źródło
1
dostarczyłeś najlepszą implementację i wykorzystałem ją do własnych celów; jednak zastanawiam się, dlaczego używasz Activator.CreateInstance(type)do tworzenia domyślnej wartości wyliczenia, a nie Enum.ToObject(type, 0). Tylko kwestia gustu?
Pierre Arnaud
1
@Pierre - Hmmm ... nie, wtedy wydawało się to bardziej naturalne :-) Może Enum.ToObject jest szybszy, ponieważ wewnętrznie wykorzystuje wewnętrzne wywołanie InternalBoxEnum? Nigdy tego nie sprawdzałem ...
Simon Mourier
2
Jak wspomniano poniżej, ale niezbyt widoczne: od .Net 4 Enum.TryParse jest dostępne i działa bez dodatkowego kodowania. Więcej informacji można znaleźć w witrynie MSDN: msdn.microsoft.com/library/vstudio/dd991317%28v=vs.100%29.aspx
Christian
16

W końcu musisz to zaimplementować wokół 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;
}

Dodatkowe uwagi:

  • Enum.TryParsejest zawarty w .NET 4. Zobacz tutaj http://msdn.microsoft.com/library/dd991876(VS.100).aspx
  • Innym podejściem byłoby bezpośrednie zawijanie Enum.Parsewychwytywania wyjątku zgłaszanego, gdy się nie powiedzie. Może to być szybsze po znalezieniu dopasowania, ale prawdopodobnie będzie wolniejsze, jeśli nie. W zależności od danych, które przetwarzasz, może to oznaczać poprawę netto lub nie.

EDYCJA: Właśnie widziałem lepszą implementację tego, która buforuje niezbędne informacje: http://damieng.com/blog/2010/10/17/enums-better-syntax-improved-performance-and-tryparse-in-net- 3-5

Richard
źródło
Chciałem zasugerować użycie default (T) do ustawienia wartości domyślnej. Okazuje się, że to nie zadziała dla wszystkich wyliczeń. Np. Jeśli typem bazowym wyliczenia był int default (T), zawsze zwróci 0, co może, ale nie musi, być poprawne dla wyliczenia.
Daniel Ballinger
Implementacja na blogu Damienga nie obsługuje wyliczeń z Flagsatrybutem.
Uwe Keim
9

Oparty na .NET 4.5

Przykładowy kod poniżej

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

Źródła: http://www.dotnetperls.com/enum-parse

Hugo Hilário
źródło
4

Mam zoptymalizowaną implementację, której możesz użyć w UnconstrainedMelody . W rzeczywistości to po prostu buforowanie listy nazwisk, ale robi to w ładny, mocno wpisany, ogólnie ograniczony sposób :)

Jon Skeet
źródło
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)) {

}
Everson Rafael
źródło
2

Obecnie nie ma po wyjęciu z pudełka Enum.TryParse. Poproszono o to w Connect ( nadal nie ma Enum.TryParse ) i otrzymano odpowiedź wskazującą możliwe włączenie do następnej platformy po .NET 3.5. Na razie musisz wdrożyć sugerowane obejścia.

Ahmad Mageed
źródło
1

Jedynym sposobem uniknięcia obsługi wyjątków jest użycie metody GetNames (), a wszyscy wiemy, że nie należy nadużywać wyjątków w przypadku typowej logiki aplikacji :)

Philippe Leybaert
źródło
1
To nie jedyny sposób. Enum.IsDefined (..) zapobiega zgłaszaniu wyjątków w kodzie użytkownika.
Thorarin
1

Czy buforowanie dynamicznie generowanej funkcji / słownika jest dozwolone?

Ponieważ nie znasz (wydaje się), że typ wyliczenia z wyprzedzeniem, pierwsze wykonanie może wygenerować coś, z czego mogłyby skorzystać kolejne wykonania.

Możesz nawet buforować wynik Enum.GetNames ()

Czy próbujesz zoptymalizować pod kątem procesora lub pamięci? Czy naprawdę potrzebujesz?

Nader Shirazie
źródło
Pomysł polega na optymalizacji procesora. Zgadzam się, że mogę to zrobić kosztem pamięci. Ale to nie jest rozwiązanie, którego szukam. Dzięki.
Manish Basantani
0

Jak już powiedzieli inni, jeśli nie używasz Try & Catch, musisz użyć IsDefined lub GetNames ... Oto kilka przykładów ... w zasadzie wszystkie są takie same, pierwsza obsługuje wyliczenia dopuszczające wartość null. Wolę drugi, ponieważ jest to rozszerzenie na ciągach, a nie wyliczenia ... ale możesz je mieszać, jak chcesz!

  • 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

źródło
0

Nie ma TryParse, ponieważ typ Enum nie jest znany do czasu uruchomienia. TryParse, który jest zgodny z tą samą metodologią, co metoda Date.TryParse, spowoduje zgłoszenie niejawnego błędu konwersji w parametrze ByRef.

Proponuję zrobić coś takiego:

//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);
    }
}
ben
źródło
Dla Trymetod, których wyniki mogą być typami wartości lub które nullmogą być prawidłowym wynikiem (np. Dictionary.TryGetValue, which has both such traits), the normal pattern is for a Metoda Try`, aby zwrócić booli przekazać wynik jako outparametr. Dla tych, które zwracają typy klas, w których nullnie jest prawidłowym wynikiem, nie ma trudności z użyciem nullzwrotu wskazać awarię
supercat,
-1

Przyjrzyj się samej klasie Enum (struct?). Jest na tym metoda Parse, ale nie jestem pewien co do tryparse.

Spence
źródło
Wiem o metodzie Enum.Parse (typeof (TEnum), strEnumValue). Zgłasza ArgumentException, jeśli parametr strEnumValue jest nieprawidłowy. Szukam TryParse ........
Manish Basantani
-2

Ta metoda skonwertuje typ wyliczenia:

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

Sprawdza typ bazowy i pobiera nazwę w celu przeanalizowania. Jeśli wszystko zawiedzie, zwróci wartość domyślną.

Naveed Ahmed
źródło
3
co to robi "Enum.GetName (typeof (HLink.ViewModels.ClaimHeaderViewModel.ClaimStatus), EnumValue)" Prawdopodobnie pewna zależność od kodu lokalnego.
Manish Basantani