Konwertuj ciąg na wyliczenie w C #


894

Jaki jest najlepszy sposób przekonwertowania łańcucha na wartość wyliczenia w C #?

Mam znacznik wyboru HTML zawierający wartości wyliczenia. Po opublikowaniu strony chcę pobrać wartość (która będzie miała postać łańcucha) i przekonwertować ją na wartość wyliczenia.

W idealnym świecie mógłbym zrobić coś takiego:

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

ale to nie jest poprawny kod.

Odpowiedzi:


1507

W .NET Core i .NET> 4 istnieje ogólna metoda analizy :

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

Obejmuje to również nowe outzmienne wbudowane w C # 7 , więc wykonuje to parsowanie, konwersję do jawnego typu wyliczenia i inicjuje + zapełnia myStatuszmienną.

Jeśli masz dostęp do C # 7 i najnowszej platformy .NET, jest to najlepszy sposób.

Oryginalna odpowiedź

W .NET jest raczej brzydki (do 4 lub więcej):

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

Zwykle upraszczam to poprzez:

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

Następnie mogę zrobić:

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

Jedną z opcji sugerowanych w komentarzach jest dodanie rozszerzenia, które jest dość proste:

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

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

Na koniec możesz chcieć mieć domyślny wyliczenie do użycia, jeśli ciąg nie może zostać przeanalizowany:

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

Co sprawia, że ​​jest to połączenie:

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

Byłbym jednak ostrożny, dodając taką metodę rozszerzenia, stringponieważ (bez kontroli przestrzeni nazw) pojawi się ona we wszystkich przypadkach, stringczy zawierają wyliczenie, czy nie ( 1234.ToString().ToEnum(StatusEnum.None)byłoby to prawidłowe, ale bezsensowne). Często najlepiej jest unikać zaśmiecania podstawowych klas Microsoft za pomocą dodatkowych metod, które mają zastosowanie tylko w bardzo specyficznych kontekstach, chyba że cały zespół programistów bardzo dobrze rozumie, co robią te rozszerzenia.


17
Jeśli wydajność jest ważna (która zawsze jest), odpowiedź chk podana przez Mckenzieg1 poniżej: stackoverflow.com/questions/16100/…
Nash

28
@avinashr ma rację co do odpowiedzi @ McKenzieG1, ale nie jest to ZAWSZE ważne. Na przykład niepotrzebna byłaby mikrooptymalizacja, aby martwić się analizowaniem enum, jeśli wykonujesz wywołanie DB dla każdej analizy.
Keith,

4
@HM Nie sądzę, aby rozszerzenie było tutaj odpowiednie - to trochę szczególny przypadek i rozszerzenie będzie miało zastosowanie do każdego łańcucha. Jeśli naprawdę chciałbyś to zrobić, byłaby to trywialna zmiana.
Keith,

7
Co powiesz na Enum.TryParse?
Elaine

15
bardzo dobrze. potrzebujesz T: struct w swoim ostatnim przykładzie.
bbrik

330

Użyj Enum.TryParse<T>(String, T)(≥ .NET 4.0):

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

Można to jeszcze bardziej uprościć, wprowadzając typ parametru C # 7.0 :

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

45
Dodaj środkowy parametr boolowski dla rozróżniania wielkości liter, a to zdecydowanie najbezpieczniejsze i najbardziej eleganckie rozwiązanie.
DanM7

18
Daj spokój, ilu z was wdrożyło tę wybraną odpowiedź z 2008 roku, aby tylko przewinąć w dół i przekonać się, że jest to lepsza (nowoczesna) odpowiedź.
TEK

@TEK Wolę odpowiedź z 2008 roku.
Zero3,

Nie rozumiem Parsegeneruje wyjątki objaśniające za to, co poszło nie tak z konwersją (wartość była null, pusta lub nie odpowiadający stałej enum), który jest lepiej niż TryParse„s logicznej wartości zwracanej (który tłumi błąd beton)
Yair

2
Enum.TryParse <T> (String, T) jest wadliwy podczas analizowania ciągów liczb całkowitych. Na przykład ten kod z powodzeniem parsuje nonsensowny ciąg znaków jako nonsensowny wyliczenie: var result = Enum.TryParse<System.DayOfWeek>("55", out var parsedEnum);
Mass Dot Net

196

Zauważ, że wydajność Enum.Parse()jest okropna, ponieważ jest realizowana poprzez odbicie. (To samo dotyczy sytuacji Enum.ToString, która przebiega w drugą stronę).

Jeśli chcesz przekonwertować ciągi znaków na Enums w kodzie wrażliwym na wydajność, najlepiej założyć Dictionary<String,YourEnum>przy starcie i użyć go do konwersji.


10
Zmierzyłem 3ms, aby przekonwertować ciąg znaków na Enum przy pierwszym uruchomieniu na komputerze stacjonarnym. (Aby zilustrować poziom okropności).
Matthieu Charbonnier

12
Wow 3ms to rząd wielkości okropności
John Stock

1
czy możesz dodać do tego przykładowy kod, abyśmy mogli dowiedzieć się, jak go wymienić i użyć
transformator

Jeśli z Twojej aplikacji korzysta 1 milion osób => daje to do 50 godzin życia, które konsumujesz :) Na jednej stronie. : P
Cătălin Rădoi


31

Możesz teraz użyć metod rozszerzenia :

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

I możesz do nich zadzwonić za pomocą poniższego kodu (tutaj FilterTypejest typem enum):

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

1
Zaktualizowałem to, aby przyjąć wartość jako obiekt i rzucić ją na ciąg znaków wewnątrz tej metody. W ten sposób mogę przyjąć wartość int .ToEnum zamiast tylko łańcuchów.
RealSollyM

2
@ SollyM Powiedziałbym, że to okropny pomysł, ponieważ ta metoda rozszerzenia będzie miała zastosowanie do wszystkich typów obiektów. Dwie metody rozszerzenia, jedna dla ciągu i jedna dla int, byłyby, moim zdaniem, czystsze i znacznie bezpieczniejsze.
Svish,

@Svish, to prawda. Jedynym powodem, dla którego to zrobiłem, jest to, że nasz kod jest używany tylko wewnętrznie i chciałem uniknąć pisania 2 rozszerzeń. A ponieważ jedyną konwersją na Enum jest łańcuch lub int, nie widziałem, żeby był inaczej.
RealSollyM,

3
@ SollyM Wewnętrzne czy nie, wciąż jestem tym, który utrzymuje i używa mojego kodu: PI byłbym zirytowany, gdybym dostał ToEnum w każdym menu inteligencji i jak mówisz, ponieważ jedyną konwersją na wyliczenie jest łańcuch lub int, możesz być całkiem pewien, że będziesz potrzebować tylko tych dwóch metod. Dwie metody nie są niczym więcej niż jedną, zwłaszcza gdy są tak małe i typu użyteczności: P
Svish

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

Więc jeśli miałeś wyliczenie o nazwie nastrój, wyglądałoby to tak:

   enum Mood
   {
      Angry,
      Happy,
      Sad
   } 

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

18

STRZEC SIĘ:

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

Enum.(Try)Parse() akceptuje wiele argumentów oddzielonych przecinkami i łączy je z binarnymi „lub”| . Nie możesz tego wyłączyć i moim zdaniem prawie nigdy tego nie chcesz.

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

Nawet jeśli Threenie został zdefiniowany, xnadal otrzyma wartość int 3. To jeszcze gorzej: Enum.Parse () może dać ci wartość, która nie jest nawet zdefiniowana dla wyliczenia!

Nie chciałbym doświadczać konsekwencji użytkowników, dobrowolnie lub niechętnie, wywołujących takie zachowanie.

Ponadto, jak wspomniali inni, wydajność jest mniejsza niż idealna dla dużych wyliczeń, a mianowicie liniowa pod względem liczby możliwych wartości.

Proponuję następujące:

    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
W rzeczywistości jest to bardzo przydatne, aby to wiedzieć Enum.(Try)Parse accepts multiple, comma-separated arguments, and combines them with binary 'or'. Oznacza, że ​​możesz ustawić swoje wartości wyliczeniowe jako potęgi 2 i masz bardzo łatwy sposób na parsowanie wielu flag boolowskich, np. „UseSSL, NoRetries, Sync”. W rzeczywistości prawdopodobnie do tego został przeznaczony.
pcdev,

16

Enum.Parse jest twoim przyjacielem:

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

13

Możesz rozszerzyć akceptowaną odpowiedź o wartość domyślną, aby uniknąć wyjątków:

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

Następnie nazywacie to tak:

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

Jeśli wartością domyślną nie jest wyliczanie, Enum.TryParse nie powiedzie się i zgłosi wyjątek, który jest przechwytywany.

Po latach używania tej funkcji w naszym kodzie w wielu miejscach może warto dodać informację, że ta operacja kosztuje wydajność!


Nie lubię wartości domyślnych. Może to prowadzić do nieprzewidzianych rezultatów.
Daniël Tulp

5
kiedy to rzuci wyjątek?
andleer

@ i jeśli wartość wyliczenia nie pasuje do tego samego typu wyliczenia co wartość domyślna
Nelly

@Nelly Stary kod tutaj, ale defaultValueoba typy zwracają metodę T. Jeśli typy są różne, pojawi się błąd czasu kompilacji: „nie można przekonwertować z„ ConsoleApp1.Size ”na„ ConsoleApp1.Color ”” ani żadnego innego typu.
andleer

@andleer, przepraszam, moja ostatnia odpowiedź była niepoprawna. Możliwe, że ta metoda zgłasza wyjątek Syste.ArgumentException w przypadku, gdy ktoś wywoła tę funkcję z wartością domyślną, która nie jest typu wyliczanie. Z wersją c # 7.0 nie mogłem stworzyć klauzuli where dla T: Enum. Właśnie dlatego złapałem tę możliwość próbując złapać.
Nelly,

8

Nie mogliśmy założyć całkowicie poprawnych danych wejściowych i wybraliśmy tę odmianę odpowiedzi @ Keitha:

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

7
// str.ToEnum<EnumType>()
T static ToEnum<T>(this string str) 
{ 
    return (T) Enum.Parse(typeof(T), str);
}

5

Analizuje ciąg znaków do TEnum bez metody try / catch i bez metody TryParse () z .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
Czy konieczne jest wykonanie opisu, jeśli kod zawiera już opis? Ok, zrobiłem to :)
jite.gs

3

Super prosty kod za pomocą TryParse:

var value = "Active";

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

2

Podoba mi się metoda metody rozszerzenia.

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

Tutaj poniżej moja implementacja z testami.

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

==================== Kompletny program ====================

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

Dla wydajności może to pomóc:

    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

Odkryłem, że tutaj nie uwzględniono przypadku z wartościami wyliczenia, które mają wartość EnumMember. Więc zaczynamy:

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

I przykład tego wyliczenia:

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

1

Musisz użyć Enum.Parse, aby uzyskać wartość obiektu z Enum, a następnie musisz zmienić wartość obiektu na określoną wartość wyliczenia. Rzutowanie na wartość wyliczoną można wykonać za pomocą Convert.ChangeType. Zobacz poniższy fragment kodu

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

1

Wypróbuj tę próbkę:

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

W tym przykładzie możesz wysłać każdy ciąg i ustawić swój Enum. Jeśli Enumposiadałeś dane, które chciałeś, zwróć je jako swój Enumtyp.


1
Nadpisujesz newModelkażdą linię, więc jeśli zawiera myślniki, nie zostanie zastąpiona. Ponadto nie musisz sprawdzać, czy ciąg zawiera coś, możesz po prostu zadzwonić Replace:var newModel = model.Replace("-", "").Replace(" ", "");
Lars Kristensen

@ LarsKristensen Tak, możemy stworzyć metodę, która usuwa znaki niealfanumeryczne.
AmirReza-Farahlagha,

1

Nie jestem pewien, kiedy to zostało dodane, ale w klasie Enum jest teraz

Parse<TEnum>(stringValue)

Używany w ten sposób w przypadku omawianego przykładu:

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

lub ignorując obudowę przez:

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

Oto zdekompilowane metody, których używa:

    [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

Jeśli nazwa właściwości jest inna niż to, co chcesz nazwać (tj. Różnice językowe), możesz to zrobić w następujący sposób:

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
    }     
}
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.