Перетворити рядок в enum в C #


894

Який найкращий спосіб перетворити рядок у значення перерахування в C #?

У мене є тег вибору HTML, що містить значення перерахунку. Коли сторінка розміщена, я хочу зібрати значення (яке буде у вигляді рядка) і перетворити його у значення перерахунку.

В ідеальному світі я міг би зробити щось подібне:

StatusEnum MyStatus = StatusEnum.Parse("Active");

але це не дійсний код.

Відповіді:


1507

У .NET Core та .NET> 4 існує загальний метод розбору :

Enum.TryParse("Active", out StatusEnum myStatus);

Сюди також входять нові вбудові outзмінні C # 7 , тому це робить пробний аналіз, перетворення на явний тип перерахунку та ініціалізує + заповнює myStatusзмінну.

Якщо у вас є доступ до C # 7 та найновішої .NET, це найкращий спосіб.

Оригінальний відповідь

У .NET це досить некрасиво (до 4 або вище):

StatusEnum MyStatus = (StatusEnum) Enum.Parse(typeof(StatusEnum), "Active", true);

Я схильний спростити це за допомогою:

public static T ParseEnum<T>(string value)
{
    return (T) Enum.Parse(typeof(T), value, true);
}

Тоді я можу зробити:

StatusEnum MyStatus = EnumUtil.ParseEnum<StatusEnum>("Active");

Один із варіантів, запропонованих у коментарях, - це додати розширення, яке досить просто:

public static T ToEnum<T>(this string value)
{
    return (T) Enum.Parse(typeof(T), value, true);
}

StatusEnum MyStatus = "Active".ToEnum<StatusEnum>();

Нарешті, можливо, ви хочете мати enum за замовчуванням для використання, якщо рядок неможливо проаналізувати:

public static T ToEnum<T>(this string value, T defaultValue) 
{
    if (string.IsNullOrEmpty(value))
    {
        return defaultValue;
    }

    T result;
    return Enum.TryParse<T>(value, true, out result) ? result : defaultValue;
}

Що робить цей дзвінок:

StatusEnum MyStatus = "Active".ToEnum(StatusEnum.None);

Однак я б обережно додав подібний метод розширення до того string, що (без контролю простору імен) він з’явиться у всіх примірниках, мають stringвони перелік, чи ні (так 1234.ToString().ToEnum(StatusEnum.None)було б дійсно, але безглуздо). Найчастіше краще уникати захаращування основних класів Microsoft додатковими методами, які застосовуються лише у дуже конкретних контекстах, якщо вся ваша команда розробників не дуже добре розуміє, що роблять ці розширення.


17
Якщо важливим є виконання (що завжди є) відповідь chk, подана Mckenzieg1 нижче: stackoverflow.com/questions/16100/…
Неш

28
@avinashr має рацію щодо відповіді @ McKenzieG1, але це ЗАВЖДИ не важливо. Наприклад, було б безглуздою мікрооптимізацією турбуватися про перебір перерахунків, якщо ви робили виклик БД для кожного розбору.
Кіт

4
@HM Я не думаю, що розширення тут доречне - це трохи особливий випадок, і розширення буде застосовуватися до кожної рядки. Якщо ви дійсно хотіли це зробити, хоча це було б тривіальною зміною.
Кіт

7
Як щодо Enum.TryParse?
Елейн

15
дуже хороший. вам потрібна структура T: у вашому останньому прикладі.
ббрік

330

Використовуйте Enum.TryParse<T>(String, T)(≥ .NET 4.0):

StatusEnum myStatus;
Enum.TryParse("Active", out myStatus);

Це можна ще більше спростити за допомогою вбудованого типу параметра C # 7.0 :

Enum.TryParse("Active", out StatusEnum myStatus);

45
Додайте середній булевий параметр для чутливості до регістру, і це найбезпечніше та найелегантніше рішення на сьогоднішній день.
DanM7

18
Давай, скільки ви реалізували обрану відповідь з 2008 року, щоб лише прокрутити вниз і виявити, що це краща (сучасна) відповідь.
ТЕК

@TEK Я фактично віддаю перевагу відповіді 2008 року.
Нуль3

Я не розумію. Parseвикидає пояснювальні винятки щодо того, що пішло не так у конверсії (значення було nullпорожнім або не має відповідної константи перерахунку), що набагато краще, ніж TryParseбулеве значення повернення '(що пригнічує конкретну помилку)
yair

2
Enum.TryParse <T> (String, T) є помилковим при аналізі цілих рядків. Наприклад, цей код буде успішно розбирати безглузду рядок як безглуздий перерахунок: var result = Enum.TryParse<System.DayOfWeek>("55", out var parsedEnum);
Mass Dot Net

196

Зауважте, що продуктивність роботи Enum.Parse()жахлива, оскільки вона реалізується за допомогою рефлексії. (Те саме стосується Enum.ToString, що йде іншим шляхом.)

Якщо вам потрібно конвертувати рядки в Enums у чутливому до продуктивності коді, найкраще зробити це Dictionary<String,YourEnum>при запуску і використовувати це для здійснення конверсій.


10
Я виміряв 3 мс, щоб перетворити рядок в Enum під час першого запуску на настільному комп'ютері. (Просто для ілюстрації рівня неповторності).
Матьє Шарбоньє

12
Вау 3 мс - жахливі порядки
Джон Сток

1
Ви можете додати зразок коду навколо цього, щоб ми отримали уявлення про те, як замінити та використовувати
трансформатор

Якщо вашим додатком користується 1 мільйон людей =>, це складає до 50 годин людського життя, яке ви споживаєте :) На одній сторінці користування. : P
Cătălin Rădoi


31

Тепер ви можете використовувати методи розширення :

public static T ToEnum<T>(this string value, bool ignoreCase = true)
{
    return (T) Enum.Parse(typeof (T), value, ignoreCase);
}

І ви можете зателефонувати їм за вказаним нижче кодом (тут FilterTypeє тип enum):

FilterType filterType = type.ToEnum<FilterType>();

1
Я оновив це, щоб прийняти значення як об'єкт і передати його рядку всередині цього методу. Таким чином я можу взяти значення int .ToEnum замість рядків.
RealSollyM

2
@SollyM Я б сказав, що це жахлива ідея, тоді цей метод розширення буде застосовуватися до всіх типів об'єктів. Два методи розширення, один для рядка та один для int, на мою думку, були б більш чистими та безпечнішими.
Свиш

@Svish, це правда. Єдина причина, що я зробив це, це те, що наш код використовується лише внутрішньо, і я хотів уникнути написання двох розширень. І оскільки єдиний раз, коли ми перетворюємось на Enum, це рядок або int, я не бачив, що це інакше проблема.
RealSollyM

3
@SollyM Internal чи ні, я все ще підтримую і використовую свій код: PI буде роздратований, якби я вставав ToEnum у кожному інтелігенційному меню, і, як ви кажете, оскільки єдиний раз, коли ви перетворюєтесь на enum, це з рядка або Int, ви можете бути впевнені, що вам знадобляться лише ці два методи. І два методи не так вже й багато, ніж один, особливо коли вони такі маленькі і корисного типу: P
Svish

20
object Enum.Parse(System.Type enumType, string value, bool ignoreCase);

Тож якби у вас був настрій із перерахуванням, це виглядатиме так:

   enum Mood
   {
      Angry,
      Happy,
      Sad
   } 

   // ...
   Mood m = (Mood) Enum.Parse(typeof(Mood), "Happy", true);
   Console.WriteLine("My mood is: {0}", m.ToString());

18

ПОВЕРНУТИСЯ:

enum Example
{
    One = 1,
    Two = 2,
    Three = 3
}

Enum.(Try)Parse() приймає кілька аргументів, розділених комами, і поєднує їх з двійковими 'або'| . Ви не можете відключити це, на мою думку, ви майже ніколи цього не бажаєте.

var x = Enum.Parse("One,Two"); // x is now Three

Навіть якби Threeне було визначено, xвсе одно отримає значення int 3. Це ще гірше: Enum.Parse () може дати вам значення, яке навіть не визначено для enum!

Я б не хотів відчувати наслідки користувачів, охоче чи не бажаючи, викликаючи таку поведінку.

Крім того, як зазначають інші, продуктивність є меншою, ніж ідеальна для великих переліків, а саме лінійна за кількістю можливих значень.

Я пропоную наступне:

    public static bool TryParse<T>(string value, out T result)
        where T : struct
    {
        var cacheKey = "Enum_" + typeof(T).FullName;

        // [Use MemoryCache to retrieve or create&store a dictionary for this enum, permanently or temporarily.
        // [Implementation off-topic.]
        var enumDictionary = CacheHelper.GetCacheItem(cacheKey, CreateEnumDictionary<T>, EnumCacheExpiration);

        return enumDictionary.TryGetValue(value.Trim(), out result);
    }

    private static Dictionary<string, T> CreateEnumDictionary<T>()
    {
        return Enum.GetValues(typeof(T))
            .Cast<T>()
            .ToDictionary(value => value.ToString(), value => value, StringComparer.OrdinalIgnoreCase);
    }

4
Насправді це дуже корисно це знати Enum.(Try)Parse accepts multiple, comma-separated arguments, and combines them with binary 'or'. Значить, ви можете встановити свої значення enum як потужність 2, і у вас є дуже простий спосіб розбору декількох булевих прапорів, наприклад. "UseSSL, NoRetries, Sync". Насправді це, напевно, те, для чого було призначено.
pcdev


13

Ви можете розширити прийняту відповідь значенням за замовчуванням, щоб уникнути виключень:

public static T ParseEnum<T>(string value, T defaultValue) where T : struct
{
    try
    {
        T enumValue;
        if (!Enum.TryParse(value, true, out enumValue))
        {
            return defaultValue;
        }
        return enumValue;
    }
    catch (Exception)
    {
        return defaultValue;
    }
}

Тоді ви називаєте це так:

StatusEnum MyStatus = EnumUtil.ParseEnum("Active", StatusEnum.None);

Якщо значення за замовчуванням не є перерахунком, то Enum.TryParse вийде з ладу і викине виняток, який зловився.

Після багатьох років використання цієї функції в нашому коді в багатьох місцях, можливо, добре додати інформацію про те, що ця операція коштує продуктивності!


Мені не подобаються значення за замовчуванням. Це може призвести до непередбачуваних результатів.
Даніель Тулп

5
коли це коли-небудь кине виняток?
andleer

@andleer, якщо значення enum не відповідає тому ж типу enum, що і значення за замовчуванням
Nelly

@Nelly Old code тут, але defaultValueтип і метод повернення мають обидва типи T. Якщо типи різні, ви отримаєте помилку часу компіляції: "Неможливо перетворити з" ConsoleApp1.Size "в" ConsoleApp1.Color "" або будь-які ваші типи.
andleer

@andleer, вибачте, моя остання відповідь тобі не була правильною. Можливо, що цей метод передає Syste.ArgumentException у випадку, якщо хтось викликає цю функцію зі значенням за замовчуванням, яке не є enum типу. З c # 7.0 я не міг скласти пункт T: Enum. Ось чому я зловив цю можливість за допомогою улову.
Неллі

8

Ми не могли припустити цілком коректне введення даних, і пішли з цим варіантом відповіді @ Keith:

public static TEnum ParseEnum<TEnum>(string value) where TEnum : struct
{
    TEnum tmp; 
    if (!Enum.TryParse<TEnum>(value, true, out tmp))
    {
        tmp = new TEnum();
    }
    return tmp;
}


5

Розбирає рядок до TEnum без методу try / catch та без методу TryParse () від .NET 4.5

/// <summary>
/// Parses string to TEnum without try/catch and .NET 4.5 TryParse()
/// </summary>
public static bool TryParseToEnum<TEnum>(string probablyEnumAsString_, out TEnum enumValue_) where TEnum : struct
{
    enumValue_ = (TEnum)Enum.GetValues(typeof(TEnum)).GetValue(0);
    if(!Enum.IsDefined(typeof(TEnum), probablyEnumAsString_))
        return false;

    enumValue_ = (TEnum) Enum.Parse(typeof(TEnum), probablyEnumAsString_);
    return true;
}

1
Чи потрібно робити опис, якщо код вже містить опис? Ок, я це зробив :)
jite.gs

3

Супер простий код за допомогою TryParse:

var value = "Active";

StatusEnum status;
if (!Enum.TryParse<StatusEnum>(value, out status))
    status = StatusEnum.Unknown;

2

Мені подобається рішення методу розширення ..

namespace System
{
    public static class StringExtensions
    {

        public static bool TryParseAsEnum<T>(this string value, out T output) where T : struct
        {
            T result;

            var isEnum = Enum.TryParse(value, out result);

            output = isEnum ? result : default(T);

            return isEnum;
        }
    }
}

Тут нижче моя реалізація з тестами.

using static Microsoft.VisualStudio.TestTools.UnitTesting.Assert;
using static System.Console;

private enum Countries
    {
        NorthAmerica,
        Europe,
        Rusia,
        Brasil,
        China,
        Asia,
        Australia
    }

   [TestMethod]
        public void StringExtensions_On_TryParseAsEnum()
        {
            var countryName = "Rusia";

            Countries country;
            var isCountry = countryName.TryParseAsEnum(out country);

            WriteLine(country);

            IsTrue(isCountry);
            AreEqual(Countries.Rusia, country);

            countryName = "Don't exist";

            isCountry = countryName.TryParseAsEnum(out country);

            WriteLine(country);

            IsFalse(isCountry);
            AreEqual(Countries.NorthAmerica, country); // the 1rst one in the enumeration
        }

1
public static T ParseEnum<T>(string value)            //function declaration  
{
    return (T) Enum.Parse(typeof(T), value);
}

Importance imp = EnumUtil.ParseEnum<Importance>("Active");   //function call

===================== Повна програма =====================

using System;

class Program
{
    enum PetType
    {
    None,
    Cat = 1,
    Dog = 2
    }

    static void Main()
    {

    // Possible user input:
    string value = "Dog";

    // Try to convert the string to an enum:
    PetType pet = (PetType)Enum.Parse(typeof(PetType), value);

    // See if the conversion succeeded:
    if (pet == PetType.Dog)
    {
        Console.WriteLine("Equals dog.");
    }
    }
}
-------------
Output

Equals dog.

1

Я використовував клас (сильно набрана версія Enum з розбором та покращенням продуктивності). Я знайшов це на GitHub, і він також повинен працювати для .NET 3.5. У неї є деяка пам'ять, оскільки вона заповнює словник.

StatusEnum MyStatus = Enum<StatusEnum>.Parse("Active");

Щоденник блогу - це Enums - кращий синтаксис, покращена продуктивність та TryParse в NET 3.5 .

І код: https://github.com/damieng/DamienGKit/blob/master/CSharp/DamienG.Library/System/EnumT.cs


1

Для продуктивності це може допомогти:

    private static Dictionary<Type, Dictionary<string, object>> dicEnum = new Dictionary<Type, Dictionary<string, object>>();
    public static T ToEnum<T>(this string value, T defaultValue)
    {
        var t = typeof(T);
        Dictionary<string, object> dic;
        if (!dicEnum.ContainsKey(t))
        {
            dic = new Dictionary<string, object>();
            dicEnum.Add(t, dic);
            foreach (var en in Enum.GetValues(t))
                dic.Add(en.ToString(), en);
        }
        else
            dic = dicEnum[t];
        if (!dic.ContainsKey(value))
            return defaultValue;
        else
            return (T)dic[value];
    }

1

Я виявив, що тут випадок із значеннями enum, які мають значення EnumMember, не розглядався. Отже, ми йдемо:

using System.Runtime.Serialization;

public static TEnum ToEnum<TEnum>(this string value, TEnum defaultValue) where TEnum : struct
{
    if (string.IsNullOrEmpty(value))
    {
        return defaultValue;
    }

    TEnum result;
    var enumType = typeof(TEnum);
    foreach (var enumName in Enum.GetNames(enumType))
    {
        var fieldInfo = enumType.GetField(enumName);
        var enumMemberAttribute = ((EnumMemberAttribute[]) fieldInfo.GetCustomAttributes(typeof(EnumMemberAttribute), true)).FirstOrDefault();
        if (enumMemberAttribute?.Value == value)
        {
            return Enum.TryParse(enumName, true, out result) ? result : defaultValue;
        }
    }

    return Enum.TryParse(value, true, out result) ? result : defaultValue;
}

І приклад цього перерахунку:

public enum OracleInstanceStatus
{
    Unknown = -1,
    Started = 1,
    Mounted = 2,
    Open = 3,
    [EnumMember(Value = "OPEN MIGRATE")]
    OpenMigrate = 4
}

1

Ви повинні використовувати Enum.Parse, щоб отримати значення об'єкта від Enum, після чого вам доведеться змінити значення об'єкта на конкретне значення enum. Передати значення перерахунку можна за допомогою Convert.ChangeType. Перегляньте наступний фрагмент коду

public T ConvertStringValueToEnum<T>(string valueToParse){
    return Convert.ChangeType(Enum.Parse(typeof(T), valueToParse, true), typeof(T));
}

1

Спробуйте цей зразок:

 public static T GetEnum<T>(string model)
    {
        var newModel = GetStringForEnum(model);

        if (!Enum.IsDefined(typeof(T), newModel))
        {
            return (T)Enum.Parse(typeof(T), "None", true);
        }

        return (T)Enum.Parse(typeof(T), newModel.Result, true);
    }

    private static Task<string> GetStringForEnum(string model)
    {
        return Task.Run(() =>
        {
            Regex rgx = new Regex("[^a-zA-Z0-9 -]");
            var nonAlphanumericData = rgx.Matches(model);
            if (nonAlphanumericData.Count < 1)
            {
                return model;
            }
            foreach (var item in nonAlphanumericData)
            {
                model = model.Replace((string)item, "");
            }
            return model;
        });
    }

У цьому зразку ви можете надіслати кожен рядок і встановити свій Enum. Якщо у вас Enumбули дані, які ви хотіли, поверніть їх як свій Enumтип.


1
Ви перезаписуєте newModelв кожному рядку, тому якщо він містить тире, він не буде замінений. Крім того, вам не доведеться перевіряти, чи містить рядок щось, ви можете просто зателефонувати Replaceвсе одно:var newModel = model.Replace("-", "").Replace(" ", "");
Ларс Крістенсен

@LarsKristensen Так, ми можемо створити метод, який видаляє неафайно-цифрові символи.
AmirReza-Farahlagha

1

Не впевнений, коли це було додано, але в класі Enum зараз є

Parse<TEnum>(stringValue)

Використовується так з відповідним прикладом:

var MyStatus = Enum.Parse<StatusEnum >("Active")

або ігнорування корпусу:

var MyStatus = Enum.Parse<StatusEnum >("active", true)

Ось декомпільовані методи, якими користуються:

    [NullableContext(0)]
    public static TEnum Parse<TEnum>([Nullable(1)] string value) where TEnum : struct
    {
      return Enum.Parse<TEnum>(value, false);
    }

    [NullableContext(0)]
    public static TEnum Parse<TEnum>([Nullable(1)] string value, bool ignoreCase) where TEnum : struct
    {
      TEnum result;
      Enum.TryParse<TEnum>(value, ignoreCase, true, out result);
      return result;
    }

0
        <Extension()>
    Public Function ToEnum(Of TEnum)(ByVal value As String, ByVal defaultValue As TEnum) As TEnum
        If String.IsNullOrEmpty(value) Then
            Return defaultValue
        End If

        Return [Enum].Parse(GetType(TEnum), value, True)
    End Function

0
public TEnum ToEnum<TEnum>(this string value, TEnum defaultValue){
if (string.IsNullOrEmpty(value))
    return defaultValue;

return Enum.Parse(typeof(TEnum), value, true);}

0

Якщо назва властивості відрізняється від того, що ви хочете викликати (тобто мовні відмінності), ви можете зробити так:

MyType.cs

using System;
using System.Runtime.Serialization;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;

[JsonConverter(typeof(StringEnumConverter))]
public enum MyType
{
    [EnumMember(Value = "person")]
    Person,
    [EnumMember(Value = "annan_deltagare")]
    OtherPerson,
    [EnumMember(Value = "regel")]
    Rule,
}

EnumExtensions.cs

using System;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;

public static class EnumExtensions
{
    public static TEnum ToEnum<TEnum>(this string value) where TEnum : Enum
    {
        var jsonString = $"'{value.ToLower()}'";
        return JsonConvert.DeserializeObject<TEnum>(jsonString, new StringEnumConverter());
    }

    public static bool EqualsTo<TEnum>(this string strA, TEnum enumB) where TEnum : Enum
    {
        TEnum enumA;
        try
        {
            enumA = strA.ToEnum<TEnum>();
        }
        catch
        {
            return false;
        }
        return enumA.Equals(enumB);
    }
}

Program.cs

public class Program
{
    static public void Main(String[] args) 
    { 
        var myString = "annan_deltagare";
        var myType = myString.ToEnum<MyType>();
        var isEqual = myString.EqualsTo(MyType.OtherPerson);
        //Output: true
    }     
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.