Как TryParse для значения Enum?

94

Я хочу написать функцию, которая может проверять заданное значение (переданное в виде строки) по возможным значениям enum. В случае совпадения он должен вернуть экземпляр enum; в противном случае он должен вернуть значение по умолчанию.

Функция может не использовать внутри внутри try/ catch, что исключает использование Enum.Parse, которое вызывает исключение при задании недопустимого аргумента.

Я хотел бы использовать что-то вроде TryParseфункции для реализации этого:

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
Я не понимаю этого вопроса; вы говорите: «Я хочу решить эту проблему, но не хочу использовать какие-либо методы, которые помогли бы мне решить эту проблему». В чем смысл?
Domenic
1
Что вы не хотите пробовать / ловить решения? Если вы пытаетесь избежать исключений из-за того, что они «дорогостоящие», дайте себе перерыв. В 99% случаев исключение Cost to throw / catch cost пренебрежимо мало по сравнению с вашим основным кодом.
SolutionYogi
1
Стоимость обработки исключений не так уж и плоха. Черт, внутренние реализации всего этого преобразования перечисления полны обработки исключений. Я действительно не люблю исключения, которые возникают и перехватываются во время нормальной логики приложения. Иногда бывает полезно отключить все генерируемые исключения (даже если они перехватываются). Разбрасывание исключений повсюду сделает их использование намного более раздражающим :)
Торарин,
3
@Domenic: Я просто ищу лучшее решение, чем то, что я уже знаю. Пойдете ли вы когда-нибудь в железнодорожный справочник, чтобы узнать маршрут или поезд, который вам уже известен :).
Маниш Басантани, 04
2
@Amby, стоимость простого ввода блока try / catch незначительна. Стоимость ВЫБРОСА исключения нет, но тогда это должно быть исключительным, не так ли? Кроме того, не говорите «мы никогда не узнаем» ... профилируйте код и узнайте. Не тратьте время на размышления, а что-то тормозит, УЗНАЙТЕ!
акмад

Ответы:

31

Как говорили другие, вы должны реализовать свой собственный TryParse . Саймон Мурье предоставляет полную реализацию, которая обо всем позаботится.

Если вы используете перечисления битовых полей (то есть флаги), вам также необходимо обрабатывать строку, например, "MyEnum.Val1|MyEnum.Val2"которая является комбинацией двух значений перечисления. Если вы просто вызовете Enum.IsDefinedэту строку, она вернет false, даже если Enum.Parseобработает ее правильно.

Обновить

Как упомянули Лиза и Кристиан в комментариях, Enum.TryParseтеперь доступен для C # в .NET4 и выше.

Документы MSDN

Виктор Арндт Мюллер
источник
Возможно, наименее сексуально, но я согласен, что это определенно лучший вариант, пока ваш код не будет перенесен на .NET 4.
Лиза
1
Как упомянуто ниже, но не совсем видно: Начиная с .Net 4 Enum.TryParse доступен и работает без дополнительного кодирования. Дополнительная информация доступна в MSDN: msdn.microsoft.com/library/vstudio/dd991317%28v=vs.100%29.aspx
Кристиан,
106

Enum.IsDefined сделает все возможное. Это может быть не так эффективно, как, вероятно, было бы TryParse, но оно будет работать без обработки исключений.

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

Стоит отметить: TryParseв .NET 4.0 был добавлен метод.

Торарин
источник
1
Лучший ответ, который я видел до сих пор ... без попыток / ловушек, без GetNames :)
Томас Левеск
13
Недостатки Enum.IsDefined: blogs.msdn.com/brada/archive/2003/11/29/50903.aspx
Надер Ширази, 04
6
также нет случая игнорирования в IsDefined
Энтони Джонстон,
2
@Anthony: если вы хотите поддерживать нечувствительность к регистру, вам понадобится GetNames. Внутренне все эти методы (в том числе Parse) используют то GetHashEntry, что делает собственное отражение - один раз. На стороне яркой, .NET 4.0 имеет TryParse, и это родовое тоже :)
Торарин
+1 Это спасло мне день! Я переношу кучу кода из .NET 4 в .NET 3.5, и вы меня спасли :)
daitangio
20

Вот кастомная реализация EnumTryParse. В отличие от других распространенных реализаций, он также поддерживает перечисление, отмеченное Flagsатрибутом.

    /// <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
вы предоставили лучшую реализацию, и я использовал ее в своих целях; однако мне интересно, почему вы используете Activator.CreateInstance(type)для создания значения перечисления по умолчанию, а не Enum.ToObject(type, 0). Дело вкуса?
Пьер Арно,
1
@Pierre - Хммм ... нет, в то время это казалось более естественным :-) Может быть, Enum.ToObject быстрее, поскольку он внутренне использует внутренний вызов InternalBoxEnum? Я никогда не проверял ...
Саймон Мурье
2
Как упомянуто ниже, но не совсем видно: Начиная с .Net 4 Enum.TryParse доступен и работает без дополнительного кодирования. Дополнительная информация доступна на MSDN: msdn.microsoft.com/library/vstudio/dd991317%28v=vs.100%29.aspx
Кристиан,
16

В конце концов, вы должны реализовать это 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;
}

Дополнительные замечания:

  • Enum.TryParseвключен в .NET 4. См. здесь http://msdn.microsoft.com/library/dd991876(VS.100).aspx
  • Другой подход состоит в том, чтобы напрямую перехватить Enum.Parseисключение, возникающее при сбое. Это может быть быстрее при обнаружении совпадения, но, скорее всего, будет медленнее, если нет. В зависимости от данных, которые вы обрабатываете, это может быть или не быть чистым улучшением.

РЕДАКТИРОВАТЬ: только что видел лучшую реализацию, которая кэширует необходимую информацию: http://damieng.com/blog/2010/10/17/enums-better-syntax-improved-performance-and-tryparse-in-net- 3-5

Ричард
источник
Я собирался предложить использовать значение по умолчанию (T), чтобы установить значение по умолчанию. Оказывается, это не сработает для всех перечислений. Например, если базовым типом для перечисления был int, по умолчанию (T) всегда будет возвращать 0, что может быть или не быть допустимым для перечисления.
Дэниел Баллинджер,
Реализация в блоге Damieng не поддерживает перечисления с Flagsатрибутом.
Уве Кейм
9

На основе .NET 4.5

Пример кода ниже

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

Ссылка: http://www.dotnetperls.com/enum-parse

Уго Хиларио
источник
4

У меня есть оптимизированная реализация, которую вы можете использовать в UnconstrainedMelody . Фактически, он просто кеширует список имен, но делает это красивым, строго типизированным и обобщенно ограниченным способом :)

Джон Скит
источник
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

В настоящее время нет готового Enum.TryParse. Это было запрошено в Connect (по- прежнему нет Enum.TryParse ), и был получен ответ, указывающий на возможное включение в следующую структуру после .NET 3.5. На данный момент вам нужно будет реализовать предлагаемые обходные пути.

Ахмад Магид
источник
1

Единственный способ избежать обработки исключений - использовать метод GetNames (), и все мы знаем, что исключениями нельзя злоупотреблять для общей логики приложения :)

Филипп Лейберт
источник
1
Это не единственный способ. Enum.IsDefined (..) предотвратит создание исключений в пользовательском коде.
Thorarin
1

Допустимо ли кеширование динамически генерируемой функции / словаря?

Поскольку вы (кажется) не знаете заранее тип перечисления, первое выполнение может сгенерировать то, чем могут воспользоваться последующие исполнения.

Вы даже можете кэшировать результат Enum.GetNames ()

Вы пытаетесь оптимизировать процессор или память? Вам действительно нужно?

Надер Ширази
источник
Идея - оптимизировать CPU. Согласитесь, что я могу сделать это за счет памяти. Но это не то решение, которое я ищу. Спасибо.
Маниш Басантани, 04
0

Как уже говорили другие, если вы не используете Try & Catch, вам нужно использовать IsDefined или GetNames ... Вот несколько примеров ... они в основном все одинаковые, первый обрабатывает перечисления, допускающие значение NULL. Я предпочитаю второй, поскольку он является расширением строк, а не перечислений ... но вы можете смешивать их, как хотите!

  • 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

Нет TryParse, потому что тип Enum неизвестен до времени выполнения. TryParse, который следует той же методологии, что и метод Date.TryParse, вызовет ошибку неявного преобразования для параметра ByRef.

Предлагаю сделать что-то вроде этого:

//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);
    }
}
Бен
источник
Для Tryметодов, результаты которых могут быть типами значений или где nullможет быть допустимый результат (например, Dictionary.TryGetValue, which has both such traits), the normal pattern is for a метод Try` для возврата boolи передачи результата в качестве outпараметра. Для тех, которые возвращают типы классов, где nullне является допустимым результатом, нет проблем с использованием nullreturn для обозначения отказа
supercat
-1

Взгляните на сам класс Enum (struct?). Для этого есть метод Parse, но я не уверен насчет трипарса.

Спенс
источник
Я знаю о методе Enum.Parse (typeof (TEnum), strEnumValue). Он генерирует исключение ArgumentException, если strEnumValue недействителен. Ищу TryParse ........
Маниш Басантани
-2

Этот метод преобразует тип перечисления:

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

Он проверяет базовый тип и сравнивает имя с ним для анализа. Если ничего не получится, он вернет значение по умолчанию.

Навид Ахмед
источник
3
что это делает "Enum.GetName (typeof (HLink.ViewModels.ClaimHeaderViewModel.ClaimStatus), EnumValue)" Вероятно, некоторая зависимость от вашего локального кода.
Маниш Басантани, 09