Преобразовать строку в перечисление в C #


896

Каков наилучший способ преобразования строки в значение перечисления в C #?

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

В идеальном мире я мог бы сделать что-то вроде этого:

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

но это не правильный код

Ответы:


1511

В .NET Core и .NET> 4 есть универсальный метод разбора :

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

Это также включает в себя новые встроенные outпеременные C # 7 , так что это делает try-parse, преобразование в явный тип enum и инициализирует + заполняет 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>();

Наконец, вы можете захотеть использовать перечисление по умолчанию, если строка не может быть проанализирована:

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
Если важна производительность (которая всегда такова), ответ ЧК, данный Маккензигом1 ниже: stackoverflow.com/questions/16100/…
Нэш

28
@avinashr прав насчет ответа @ McKenzieG1, но это не всегда важно. Например, было бы бессмысленной микрооптимизацией беспокоиться о разборе enum, если вы выполняете вызов БД для каждого разбора.
Кит

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

7
Как насчет Enum.TryParse?
Элейн

15
очень хорошо. вам нужно где T: struct в вашем последнем примере.
bbrik

331

Используйте 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

@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
Я обновил это, чтобы принять значение в качестве объекта и привести его к строке внутри этого метода. Таким образом, я могу принять значение типа .ToEnum вместо только строк.
RealSollyM

2
@ SollyM Я бы сказал, что это ужасная идея, потому что тогда этот метод расширения будет применяться ко всем типам объектов. На мой взгляд, два метода расширения, один для строки и один для int, будут чище и намного безопаснее.
Свиш

@ Свист, это правда. Единственная причина, по которой я это сделал, заключается в том, что наш код используется только для внутреннего использования, и я хотел избежать написания двух расширений. И поскольку мы конвертируем в Enum единственный раз с помощью string или int, я не вижу в этом проблемы.
RealSollyM

3
@SollyM Внутренний или нет, я все еще поддерживаю и использую свой код: PI будет раздражен, если я вызову ToEnum в каждом меню intellisense, и, как вы говорите, поскольку единственный раз, когда вы конвертируете в 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

BEWARE:

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

Enum.(Try)Parse() принимает несколько аргументов, разделенных запятыми, и объединяет их с двоичным «или»| . Вы не можете отключить это, и, по моему мнению, вы почти никогда не хотите этого.

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

Даже если Threeне определено, xвсе равно получит значение int3 . Это еще хуже: 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'. Означает, что вы можете установить значения перечисления как степени 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, Enum.TryParse завершится ошибкой и выдаст исключение, которое перехватывается.

После многих лет использования этой функции в нашем коде во многих местах может быть полезно добавить информацию о том, что эта операция стоит производительности!


Я не люблю значения по умолчанию. Это может привести к непредсказуемым результатам.
Даниэль Тулп

5
когда это когда-нибудь вызовет исключение?
andleer

@andleer, если значение перечисления не соответствует тому же типу перечисления, что и значение по умолчанию
Нелли

@Nelly Старый код здесь, но defaultValueтип возвращаемого значения и метод оба имеют тип T. Если типы различаются, вы получите ошибку времени компиляции: «невозможно преобразовать из« ConsoleApp1.Size »в« ConsoleApp1.Color »» или что-то еще из ваших типов.
andleer

@andleer, извини, мой последний ответ был неверным. Возможно, что этот метод генерирует исключение Syste.ArgumentException в случае, если кто-то вызывает эту функцию со значением по умолчанию, отличным от типа enum. С c # 7.0 я не мог сделать предложение where 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

Я обнаружил, что здесь случай со значениями перечисления, которые имеют значение 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, после этого вы должны изменить значение объекта на конкретное значение перечисления. Приведение к перечисляемому значению можно выполнить с помощью 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.