Як спробуватиParse для значення Enum?


94

Я хочу написати функцію, яка може перевірити дане значення (передане у вигляді рядка) щодо можливих значень 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
Я не розумію цього питання; Ви говорите: "Я хочу вирішити цю проблему, але я не хочу використовувати жоден із методів, який дав би мені рішення". В чому справа?
Доменік

1
Яка ваша огида намагатись / ловити рішення? Якщо ви намагаєтеся уникнути винятків, оскільки вони "дорогі", будь ласка, дайте собі відпочити. У 99% випадків виняток витрат на кидок / зловити є незначним у порівнянні з вашим основним кодом.
SolutionYogi

1
Вартість обробки винятків не така вже й погана. До біса, внутрішні реалізації всього цього перетворення перерахування повні обробки винятків. Мені справді не подобається винятки, коли їх кидають і ловлять під час звичайної логіки додатків. Іноді може бути корисно розірвати всі винятки (навіть коли їх зловлять). Кидання винятків повсюдно зробить це набагато прикрішим для використання :)
Thorarin

3
@Domenic: Я просто шукаю кращого рішення, ніж те, що я вже знаю. Чи хотіли б ви коли-небудь звернутися до залізничного запиту, щоб попросити маршрут або поїзд, який ви вже знаєте :).
Маніш Басантані

2
@Amby, вартість простого введення блоку try / catch є незначною. Вартість кидання винятку не є, але тоді це повинно бути винятковим, ні? Крім того, не кажіть "ми ніколи не знаємо" ... профілюйте код і дізнавайтесь. Не витрачайте час на роздуми, якщо щось повільне, ЗНАЙДІТЬ!
akmad

Відповіді:


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 :)
Thomas Levesque


6
також немає випадків ігнорування щодо IsDefined
Ентоні Джонстон,

2
@Anthony: якщо ви хочете підтримати нечутливість до регістру, вам знадобиться GetNames. Внутрішньо всі ці методи (включаючи Parse) використовують GetHashEntry, що робить фактичне відображення - один раз. Яскравою стороною є те, що .NET 4.0 має TryParse, і він також є загальним :)
Thorarin

+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). Лише питання смаку?
Pierre Arnaud

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, що може чи не бути дійсним для переліку.
Даніель Балінджер,

Реалізація в блозі Дамієнга не підтримує перелічення з 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 ()

Ви намагаєтеся оптимізувати для процесора або пам'яті? Вам справді потрібно?


Ідея полягає в оптимізації процесора. Погодьтеся, що я можу це зробити за пам'ять витрат. Але це не рішення, яке я шукаю. Дякую.
Маніш Басантані

0

Як вже говорили інші, якщо ви не використовуєте функцію 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

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?). Існує метод синтаксичного аналізу, але я не впевнений у трипарі.


Я знаю про метод 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)" Можливо, якась залежність від вашого локального коду.
Маніш Басантані
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.