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

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

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

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

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

но это неверный код.

Ответов (25)

Решение

В .NET Core и .NET Framework ≥4.0 есть общий метод синтаксического анализа :

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

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

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 дополнительными методами, которые применяются только в очень специфических контекстах, если вся ваша команда разработчиков не очень хорошо понимает, что эти расширения делают.

Обратите внимание, что производительность Enum.Parse() ужасна, потому что она реализована через отражение. (То же Enum.ToString самое и в обратном направлении.)

Если вам нужно преобразовать строки в Enums в коде, чувствительном к производительности, лучше всего создать Dictionary<String,YourEnum> при запуске и использовать его для преобразования.

Мы не могли предположить, что вводимые данные являются абсолютно правильными, и выбрали этот вариант ответа @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;
}

Разбирает строку в 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;
}

Используйте Enum.TryParse<T>(String, T)(≥ .NET 4.0):

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

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

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

Теперь вы можете использовать методы расширения :

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

И вы можете вызвать их с помощью приведенного ниже кода (здесь FilterType это тип перечисления):

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

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

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 завершится ошибкой и выдаст исключение, которое будет перехвачено.

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

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.

Мне нравится решение метода расширения ..

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
        }

ОСТЕРЕГАТЬСЯ:

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 () может дать вам значение, которое даже не определено для перечисления!

Я бы не хотел испытать на себе последствия того, что пользователи, вольно или невольно, запускают такое поведение.

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

Предлагаю следующее:

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

Для производительности это может помочь:

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

Я обнаружил, что здесь случай со значениями перечисления, имеющими значение 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
}

Супер простой код с использованием TryParse:

var value = "Active";

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

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

public T ConvertStringValueToEnum<T>(string valueToParse){
    return Convert.ChangeType(Enum.Parse(typeof(T), valueToParse, true), typeof(T));
}
public TEnum ToEnum<TEnum>(this string value, TEnum defaultValue){
if (string.IsNullOrEmpty(value))
    return defaultValue;

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

Не уверен, когда это было добавлено, но в классе 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;
    }

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

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

Прежде всего, вам нужно украсить ваше перечисление, например:

    public enum Store : short
{
    [Description("Rio Big Store")]
    Rio = 1
}

в .net 5 я создаю этот метод расширения:

//The class also needs to be static, ok?
public static string GetDescription(this System.Enum enumValue)
    {
        FieldInfo fi = enumValue.GetType().GetField(enumValue.ToString());

        DescriptionAttribute[] attributes = (DescriptionAttribute[])fi.GetCustomAttributes(
            typeof(DescriptionAttribute), false);

        if (attributes != null && attributes.Length > 0) return attributes[0].Description;
        else return enumValue.ToString();
    }

теперь у вас есть методы расширения для использования в любых перечислениях

Нравится:

var Desc = Store.Rio.GetDescription(); //Store is your Enum
        <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

Вы ищете Enum.Parse .

SomeEnum enum = (SomeEnum)Enum.Parse(typeof(SomeEnum), "EnumValue");
// str.ToEnum<EnumType>()
T static ToEnum<T>(this string str) 
{ 
    return (T) Enum.Parse(typeof(T), str);
}
object Enum.Parse(System.Type enumType, string value, bool ignoreCase);

Итак, если бы у вас было перечисление с именем mood, оно выглядело бы так:

   enum Mood
   {
      Angry,
      Happy,
      Sad
   } 

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

Enum.Parse - ваш друг:

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

Я использовал класс (строго типизированная версия 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

Если вы хотите использовать значение по умолчанию, когда оно пустое или пустое (например, при извлечении из файла конфигурации, а значение не существует), и генерировать исключение, когда строка или число не соответствуют ни одному из значений перечисления. Остерегайтесь оговорок в ответе Тимо ( https://answacode.com/a/34267134/2454604 ).

    public static T ParseEnum<T>(this string s, T defaultValue, bool ignoreCase = false) 
        where T : struct, IComparable, IConvertible, IFormattable//If C# >=7.3: struct, System.Enum 
    {
        if ((s?.Length ?? 0) == 0)
        {
            return defaultValue;
        }

        var valid = Enum.TryParse<T>(s, ignoreCase, out T res);

        if (!valid || !Enum.IsDefined(typeof(T), res))
        {
            throw new InvalidOperationException(
                $"'{s}' is not a valid value of enum '{typeof(T).FullName}'!");
        }
        return res;
    }