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

Я хочу написать функцию, которая может проверять заданное значение (переданное как строка) на возможные значения 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;
}

Ответ 1

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

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

Обновить

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

Документы MSDN

Ответ 2

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

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

Ответ 3

Вот пример реализации 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;
    }

Ответ 4

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

Ответ 5

На основе .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

Ответ 6

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

Ответ 7

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

Ответ 8

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

Ответ 9

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

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

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

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

Ответ 10

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)) {

}

Ответ 11

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

  • 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

Ответ 12

Существует не 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);
    }
}

Ответ 13

Посмотрите на класс Enum (struct?). На этом есть метод Parse, но я не уверен в tryparse.

Ответ 14

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

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

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