Convierta una cadena en una enumeración en C #

896

¿Cuál es la mejor manera de convertir una cadena en un valor de enumeración en C #?

Tengo una etiqueta de selección HTML que contiene los valores de una enumeración. Cuando se publica la página, quiero recoger el valor (que tendrá la forma de una cadena) y convertirlo al valor de enumeración.

En un mundo ideal, podría hacer algo como esto:

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

Pero ese no es un código válido.

Ben Mills
fuente

Respuestas:

1511

En .NET Core y .NET> 4 hay un método de análisis genérico :

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

Esto también incluye las nuevas outvariables en línea de C # 7 , por lo que realiza la conversión de prueba, conversión al tipo de enumeración explícita e inicializa + llena la myStatusvariable.

Si tiene acceso a C # 7 y al último .NET, esta es la mejor manera.

Respuesta original

En .NET es bastante feo (hasta 4 o superior):

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

Tiendo a simplificar esto con:

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

Entonces puedo hacer:

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

Una opción sugerida en los comentarios es agregar una extensión, que es lo suficientemente simple:

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

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

Finalmente, es posible que desee tener una enumeración predeterminada para usar si la cadena no se puede analizar:

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

Lo que hace que esta sea la llamada:

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

Sin embargo, sería cuidadoso al agregar un método de extensión como este, stringya que (sin control de espacio de nombres) aparecerá en todas las instancias de stringsi tienen una enumeración o no (por 1234.ToString().ToEnum(StatusEnum.None)lo que sería válido pero sin sentido). A menudo es mejor evitar abarrotar las clases principales de Microsoft con métodos adicionales que solo se aplican en contextos muy específicos, a menos que todo su equipo de desarrollo comprenda muy bien lo que hacen esas extensiones.

Keith
fuente
17
Si el rendimiento es importante (que siempre lo es), responda chk dada por Mckenzieg1 a continuación: stackoverflow.com/questions/16100/…
Nash
28
@avinashr tiene razón acerca de la respuesta de @ McKenzieG1, pero no es SIEMPRE importante. Por ejemplo, sería una micro optimización inútil preocuparse por el análisis de enumeración si estuviera haciendo una llamada de DB para cada análisis.
Keith el
44
@HM No creo que una extensión sea apropiada aquí: es un caso especial y una extensión se aplicaría a cada cadena. Si realmente quisieras hacerlo, sería un cambio trivial.
Keith
77
¿Qué tal Enum. TryParse?
Elaine
15
muy agradable. necesitas un where T: struct en tu último ejemplo.
bbrik
331

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

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

Se puede simplificar aún más con la alineación de tipos de parámetros de C # 7.0 :

Enum.TryParse("Active", out StatusEnum myStatus);
Erwin Mayer
fuente
45
Agregue el parámetro booleano medio para la distinción entre mayúsculas y minúsculas y esta es la solución más segura y elegante con diferencia.
DanM7
18
Vamos, cuántos de ustedes implementaron esa respuesta seleccionada de 2008 para solo desplazarse hacia abajo y encontrar que esta es la mejor respuesta (moderna).
TEK
@TEK En realidad prefiero la respuesta de 2008.
Zero3
No lo entiendo Parselanza excepciones explicativas de lo que salió mal con la conversión (el valor era null, vacío o sin constante de enumeración correspondiente), que es mucho mejor que TryParseel valor de retorno booleano (que suprime el error concreto)
Yair
2
Enum. TryParse <T> (String, T) es defectuoso al analizar cadenas enteras. Por ejemplo, este código analizará con éxito una cadena sin sentido como una enumeración sin sentido: var result = Enum.TryParse<System.DayOfWeek>("55", out var parsedEnum);
Mass Dot Net
196

Tenga en cuenta que el rendimiento de Enum.Parse()es horrible, ya que se implementa a través de la reflexión. (Lo mismo es cierto de lo Enum.ToStringque va para otro lado).

Si necesita convertir cadenas a Enums en código sensible al rendimiento, su mejor opción es crear un Dictionary<String,YourEnum>inicio y utilizarlo para realizar sus conversiones.

McKenzieG1
fuente
10
He medido 3 ms para convertir una cadena a Enum en la primera ejecución, en una computadora de escritorio. (Solo para ilustrar el nivel de horror).
Matthieu Charbonnier
12
Wow 3ms es una orden de magnitud terrible
John Stock
1
¿puede agregar una muestra de código alrededor de esto, para que tengamos una idea de cómo reemplazar y usar
transformador
Si su aplicación es utilizada por 1 millón de personas => agrega hasta 50 horas de vida humana que está consumiendo :) En una sola página de uso. : P
Cătălin Rădoi
95

Estás buscando Enum .

SomeEnum enum = (SomeEnum)Enum.Parse(typeof(SomeEnum), "EnumValue");
DavidWhitney
fuente
31

Puede usar métodos de extensión ahora:

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

Y puede llamarlos por el siguiente código (aquí, FilterTypees un tipo de enumeración):

FilterType filterType = type.ToEnum<FilterType>();
Foyzul Karim
fuente
1
He actualizado esto para tomar el valor como objeto y convertirlo en una cadena dentro de este método. De esta manera, puedo tomar un valor int .ToEnum en lugar de solo cadenas.
RealSollyM
2
@SollyM Diría que es una idea horrible porque este método de extensión se aplicará a todos los tipos de objetos. Dos métodos de extensión, uno para string y otro para int, serían más limpios y mucho más seguros en mi opinión.
Svish
@Svish, eso es cierto. La única razón por la que hice esto es porque nuestro código se usa internamente y quería evitar escribir 2 extensiones. Y dado que la única vez que convertimos a Enum es con string o int, de lo contrario no vi que fuera un problema.
RealSollyM
3
@SollyM Internal o no, sigo siendo el que mantiene y usa mi código: PI estaría molesto si obtuviera un ToEnum en cada menú de intellisense, y como usted dice, ya que la única vez que convierte a un enum es desde una cadena o int, puedes estar bastante seguro de que solo necesitarás esos dos métodos. Y dos métodos no son mucho más que uno, especialmente cuando son tan pequeños y del tipo de utilidad: P
Svish
20
object Enum.Parse(System.Type enumType, string value, bool ignoreCase);

Entonces, si tuviera una enumeración llamada estado de ánimo, se vería así:

   enum Mood
   {
      Angry,
      Happy,
      Sad
   } 

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

TENER CUIDADO:

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

Enum.(Try)Parse() acepta múltiples argumentos separados por comas y los combina con binarios 'o'| . No puede desactivar esto y, en mi opinión, casi nunca lo quiere.

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

Incluso si Threeno se definió, xtodavía obtendría el valor int 3. Eso es aún peor: ¡Enum.Parse () puede darle un valor que ni siquiera está definido para la enumeración!

No quisiera experimentar las consecuencias de los usuarios, voluntaria o involuntariamente, que desencadenan este comportamiento.

Además, como lo mencionaron otros, el rendimiento es menos que ideal para grandes enumeraciones, es decir, lineal en la cantidad de valores posibles.

Sugiero lo siguiente:

    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);
    }
Timo
fuente
44
De hecho, es muy útil saber eso Enum.(Try)Parse accepts multiple, comma-separated arguments, and combines them with binary 'or'. Significa que puede configurar sus valores de enumeración como potencias de 2 y tiene una manera muy fácil de analizar múltiples banderas booleanas, por ejemplo. "UseSSL, NoRetries, Sync". De hecho, eso es probablemente para lo que fue diseñado.
pcdev
16

Enum.Parse es tu amigo:

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

Puede ampliar la respuesta aceptada con un valor predeterminado para evitar excepciones:

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

Entonces lo llamas así:

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

Si el valor predeterminado no es una enumeración, Enum. TryParse fallará y generará una excepción que se detecte.

Después de años de usar esta función en nuestro código en muchos lugares, ¡tal vez sea bueno agregar la información de que esta operación cuesta rendimiento!

Nelly
fuente
No me gustan los valores predeterminados. Puede conducir a resultados impredecibles.
Daniël Tulp
55
¿Cuándo arrojará esto alguna vez una excepción?
andleer
@andleer si el valor de enumeración no se ajusta al mismo tipo de enumeración que el valor predeterminado
Nelly
El código @Nelly Old aquí, pero el defaultValuetipo de retorno y el método son ambos de tipo T. Si los tipos son diferentes, recibirá un error de tiempo de compilación: "no se puede convertir de 'ConsoleApp1.Size' a 'ConsoleApp1.Color'" o cualesquiera que sean sus tipos.
andleer
@andleer, lo siento, mi última respuesta no fue correcta. Es posible que este método arroje una excepción Syste.ArgumentException en el caso de que alguien llame a esta función con un valor predeterminado que no sea de tipo enum. Con c # 7.0 no pude hacer una cláusula where de T: Enum. Es por eso que atrapé esta posibilidad con un intento de captura.
Nelly
8

No pudimos asumir una entrada perfectamente válida, y seguimos con esta variación de la respuesta de @ 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;
}
brecha
fuente
7
// str.ToEnum<EnumType>()
T static ToEnum<T>(this string str) 
{ 
    return (T) Enum.Parse(typeof(T), str);
}
Mark Cidade
fuente
5

Analiza cadenas a TEnum sin try / catch y sin el método TryParse () de .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;
}
jite.gs
fuente
1
¿Es necesario hacer una descripción si el código ya contiene una descripción? Ok, hice esto :)
jite.gs
3

Código super simple usando TryParse:

var value = "Active";

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

Me gusta la solución del método de extensión.

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

Aquí debajo mi implementación con pruebas.

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
        }
alhpe
fuente
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 programa 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.
Rae Lee
fuente
1

Utilicé class (versión fuertemente tipada de Enum con análisis y mejoras de rendimiento). Lo encontré en GitHub, y también debería funcionar para .NET 3.5. Tiene algo de memoria por encima ya que almacena un diccionario.

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

La publicación de blog es Enums: mejor sintaxis, rendimiento mejorado y TryParse en NET 3.5 .

Y código: https://github.com/damieng/DamienGKit/blob/master/CSharp/DamienG.Library/System/EnumT.cs

Patrik Lindström
fuente
1

Para el rendimiento, esto podría ayudar:

    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];
    }
Koray
fuente
1

Encontré que aquí no se consideró el caso con valores enum que tienen el valor EnumMember. Así que, aquí vamos:

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

Y ejemplo de esa enumeración:

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

Debe usar Enum.Parse para obtener el valor del objeto de Enum, después de eso debe cambiar el valor del objeto a un valor de enumeración específico. La conversión al valor de enumeración se puede hacer utilizando Convert.ChangeType. Por favor, eche un vistazo al siguiente fragmento de código

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

Prueba esta muestra:

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

En este ejemplo, puede enviar cada cadena y establecer su Enum. Si Enumtenía datos que deseaba, devuélvalos como su Enumtipo.

AmirReza-Farahlagha
fuente
1
Está sobrescribiendo newModelen cada línea, por lo que si contiene guiones, no se reemplazará. Además, no tiene que verificar si la cadena contiene algo, simplemente puede llamar de Replacetodos modos:var newModel = model.Replace("-", "").Replace(" ", "");
Lars Kristensen
@LarsKristensen Sí, podemos crear un método que elimine los caracteres no alfanuméricos.
AmirReza-Farahlagha
1

No estoy seguro de cuándo se agregó esto, pero en la clase Enum ahora hay un

Parse<TEnum>(stringValue)

Usado así con el ejemplo en cuestión:

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

o ignorando la carcasa por:

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

Aquí están los métodos descompilados que usa:

    [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;
    }
JCisar
fuente
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
RABEE AHMED
fuente
0
public TEnum ToEnum<TEnum>(this string value, TEnum defaultValue){
if (string.IsNullOrEmpty(value))
    return defaultValue;

return Enum.Parse(typeof(TEnum), value, true);}
RABEE AHMED
fuente
0

Si el nombre de la propiedad es diferente del que desea llamarlo (es decir, diferencias de idioma), puede hacer lo siguiente:

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
    }     
}
Joel Wiklund
fuente