Converti una stringa in un enum in C #


895

Qual è il modo migliore per convertire una stringa in un valore di enumerazione in C #?

Ho un tag di selezione HTML contenente i valori di un'enumerazione. Quando la pagina viene pubblicata, desidero raccogliere il valore (che sarà in forma di stringa) e convertirlo nel valore di enumerazione.

In un mondo ideale, potrei fare qualcosa del genere:

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

ma questo non è un codice valido.

Risposte:


1510

In .NET Core e .NET> 4 esiste un metodo di analisi generico :

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

Ciò include anche le nuove outvariabili inline di C # 7 , quindi esegue la prova-analisi, la conversione nel tipo enum esplicito e inizializza + popola la myStatusvariabile.

Se hai accesso a C # 7 e all'ultimo .NET, questo è il modo migliore.

Risposta originale

In .NET è piuttosto brutto (fino al 4 o superiore):

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

Tendo a semplificare questo con:

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

Quindi posso fare:

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

Un'opzione suggerita nei commenti è l'aggiunta di un'estensione, che è abbastanza semplice:

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

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

Infine, potresti voler avere un enum predefinito da usare se la stringa non può essere analizzata:

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

Il che rende questa la chiamata:

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

Tuttavia, starei attento ad aggiungere un metodo di estensione come questo in stringquanto (senza controllo dello spazio dei nomi) apparirà su tutte le istanze del stringpossesso di un enum o meno (quindi 1234.ToString().ToEnum(StatusEnum.None)sarebbe valido ma senza senso). Spesso è meglio evitare di ingombrare le classi principali di Microsoft con metodi extra che si applicano solo in contesti molto specifici, a meno che l'intero team di sviluppo non abbia un'ottima conoscenza di ciò che fanno quelle estensioni.


17
Se performace è importante (che è sempre) chk risposta data da Mckenzieg1 di seguito: stackoverflow.com/questions/16100/…
Nash

28
@avinashr ha ragione sulla risposta di @ McKenzieG1, ma non è SEMPRE importante. Ad esempio, sarebbe una micro ottimizzazione inutile preoccuparsi dell'analisi enum se si effettuava una chiamata DB per ogni analisi.
Keith,

4
@HM Non penso che un'estensione sia appropriata qui - è un po 'un caso speciale e un'estensione si applicherebbe a ogni stringa. Se davvero volessi farlo, sarebbe un cambiamento insignificante.
Keith,

7
Che ne dici di Enum. TryParse?
Elaine,

15
molto bella. hai bisogno di una T: struct nel tuo ultimo esempio.
bbrik,

331

Usa Enum.TryParse<T>(String, T)(≥ .NET 4.0):

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

Può essere ulteriormente semplificato con il tipo di parametro C # 7.0 integrato :

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

45
Aggiungi il parametro booleano medio per la distinzione tra maiuscole e minuscole e questa è di gran lunga la soluzione più sicura ed elegante.
DanM7,

18
Dai, quanti di voi hanno implementato quella risposta selezionata dal 2008 per scorrere solo verso il basso e scoprire che questa è la risposta migliore (moderna).
TEK

@TEK In realtà preferisco la risposta del 2008.
Zero3

Non capisco Parsegenera eccezioni esplicative per ciò che è andato storto con la conversione (valore era null, vuoto o nessuna costante enum corrispondente), che è molto meglio del TryParsevalore di ritorno booleano (che sopprime l'errore concreto)
yair

2
Enum.TryParse <T> (String, T) è difettoso durante l'analisi delle stringhe di numeri interi. Ad esempio, questo codice analizzerà correttamente una stringa senza senso come enum senza senso: var result = Enum.TryParse<System.DayOfWeek>("55", out var parsedEnum);
Mass Dot Net

196

Si noti che la performance di Enum.Parse()è terribile, perché è implementata tramite la riflessione. (Lo stesso vale Enum.ToString, che va dall'altra parte.)

Se hai bisogno di convertire stringhe in Enum in codice sensibile alle prestazioni, la soluzione migliore è crearle Dictionary<String,YourEnum>all'avvio e usarle per fare le tue conversioni.


10
Ho misurato 3 ms per convertire una stringa in un Enum al primo avvio, su un computer desktop. (Giusto per illustrare il livello di orribile).
Matthieu Charbonnier,

12
Wow 3ms è ordini di grandezza di terribile
John Stock

1
puoi aggiungere un esempio di codice attorno a questo, in modo da avere un'idea su come sostituire e utilizzare
trasformatore

Se la tua app viene utilizzata da 1 milione di persone => aggiunge fino a 50 ore di vita umana che stai consumando :) In una sola pagina. : P
Cătălin Rădoi,


31

Puoi utilizzare i metodi di estensione ora:

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

E puoi chiamarli con il seguente codice (qui, FilterTypeè un tipo enum):

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

1
Ho aggiornato questo per prendere il valore come oggetto e inserirlo nella stringa all'interno di questo metodo. In questo modo posso prendere un valore int. ToEnum invece che solo stringhe.
RealSollyM,

2
@SollyM Direi che è un'idea orribile perché questo metodo di estensione si applicherà a tutti i tipi di oggetti. Due metodi di estensione, uno per stringa e uno per int, sarebbero più puliti e molto più sicuri secondo me.
Svish,

@Svish, è vero. L'unica ragione per cui l'ho fatto è perché il nostro codice è usato solo internamente e volevo evitare di scrivere 2 estensioni. E poiché l'unica volta che ci convertiamo in Enum è con string o int, non ho visto che fosse un problema altrimenti.
RealSollyM il

3
@SollyM Interno o no, sono ancora quello che mantiene e usa il mio codice: PI sarebbe infastidito se avessi creato un ToEnum in ogni menu di intellisense, e come dici tu, poiché l'unica volta che ti converti in un enum è da stringa o int, puoi essere abbastanza sicuro che avrai solo bisogno di quei due metodi. E due metodi non sono molto più di uno, specialmente quando sono così piccoli e del tipo di utilità: P
Svish

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

Quindi se avessi un enum chiamato umore sarebbe simile a questo:

   enum Mood
   {
      Angry,
      Happy,
      Sad
   } 

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

18

ATTENZIONE:

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

Enum.(Try)Parse() accetta più argomenti separati da virgola e li combina con "o" binario| . Non puoi disabilitarlo e secondo me non lo vuoi quasi mai.

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

Anche se Threenon fosse definito, xotterrebbe comunque il valore int 3. È ancora peggio: Enum.Parse () può darti un valore che non è nemmeno definito per l'enum!

Non vorrei sperimentare le conseguenze degli utenti, volontariamente o involontariamente, innescando questo comportamento.

Inoltre, come menzionato da altri, le prestazioni sono meno che ideali per grandi enumerazioni, vale a dire lineari nel numero di valori possibili.

Suggerisco quanto segue:

    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
In effetti è molto utile saperlo Enum.(Try)Parse accepts multiple, comma-separated arguments, and combines them with binary 'or'. Significa che puoi impostare i tuoi valori enum come potenze di 2 e hai un modo molto semplice per analizzare più flag booleani, ad es. "UseSSL, NoRetries, Sync". In effetti è probabilmente quello per cui è stato progettato.
pcdev,

16

Enum.Parse è il tuo amico:

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

13

È possibile estendere la risposta accettata con un valore predefinito per evitare eccezioni:

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

Quindi lo chiami come:

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

Se il valore predefinito non è un enum, Enum. TryParse fallisce e genera un'eccezione che viene rilevata.

Dopo anni di utilizzo di questa funzione nel nostro codice in molti luoghi, forse è bene aggiungere le informazioni che questa operazione costa le prestazioni!


Non mi piacciono i valori predefiniti. Può portare a risultati imprevedibili.
Daniël Tulp,

5
quando sarà mai un'eccezione?
andleer,

@andleer se il valore enum non si adatta allo stesso tipo enum del valore predefinito
Nelly

@Nelly Il vecchio codice qui, ma il tipo defaultValuee il tipo restituito sono entrambi di tipo T. Se i tipi sono diversi, riceverai un errore di compilazione: "impossibile convertire da 'ConsoleApp1.Size' a 'ConsoleApp1.Color'" o qualunque sia il tuo tipo.
Andleer

@andleer, mi dispiace che la mia ultima risposta non sia corretta. È possibile che questo metodo generi un'eccezione Syste.ArgumentException nel caso in cui qualcuno chiami questa funzione con un valore predefinito che non è di tipo enum. Con c # 7.0 non sono riuscito a creare una clausola where di T: Enum. Ecco perché ho colto questa possibilità con un tentativo di cattura.
Nelly,

8

Non potevamo assumere un input perfettamente valido e abbiamo seguito questa variante della risposta di @ 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;
}

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

5

Analizza la stringa su TEnum senza try / catch e senza il metodo TryParse () da .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
Se è necessario fare una descrizione se il codice contiene già una descrizione? Ok, l'ho fatto :)
jite.gs

3

Codice super semplice usando TryParse:

var value = "Active";

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

2

Mi piace la soluzione del metodo di estensione ..

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

Di seguito la mia implementazione con i test.

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

==================== Un programma completo ====================

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

Ho usato la classe (versione fortemente tipizzata di Enum con analisi e miglioramenti delle prestazioni). L'ho trovato su GitHub e dovrebbe funzionare anche per .NET 3.5. Presenta un sovraccarico di memoria poiché memorizza un dizionario.

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

Il blogpost è Enums - Migliore sintassi, prestazioni migliorate e TryParse in NET 3.5 .

E codice: https://github.com/damieng/DamienGKit/blob/master/CSharp/DamienG.Library/System/EnumT.cs


1

Per prestazioni questo potrebbe aiutare:

    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

Ho scoperto che qui il caso con valori enum che hanno valore EnumMember non è stato considerato. Quindi eccoci qui:

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

Ed esempio di quell'enum:

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

1

Devi usare Enum.Parse per ottenere il valore dell'oggetto da Enum, dopodiché devi cambiare il valore dell'oggetto in un valore enum specifico. Il cast per enumare il valore può essere fatto usando Convert.ChangeType. Dai un'occhiata al seguente frammento di codice

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

1

Prova questo esempio:

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

In questo esempio puoi inviare ogni stringa e impostare il tuo Enum. Se Enumdisponevi dei dati desiderati, restituiscili come Enumtipo.


1
Stai sovrascrivendo newModelsu ogni riga, quindi se contiene trattini, non verrà sostituito. Inoltre, non devi controllare se la stringa contiene qualcosa, puoi semplicemente chiamare Replacecomunque:var newModel = model.Replace("-", "").Replace(" ", "");
Lars Kristensen,

@LarsKristensen Sì, possiamo creare un metodo per rimuovere il carattere non alfanumerico.
AmirReza-Farahlagha,

1

Non sono sicuro quando questo è stato aggiunto, ma sulla classe Enum ora c'è un

Parse<TEnum>(stringValue)

Usato così con l'esempio in questione:

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

o ignorando l'involucro di:

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

Ecco i metodi decompilati che utilizza:

    [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

Se il nome della proprietà è diverso da come lo si desidera chiamare (ad esempio differenze di lingua), è possibile eseguire questa operazione:

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
    }     
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.