¿Cómo TryParse para el valor Enum?

94

Quiero escribir una función que pueda validar un valor dado (pasado como una cadena) contra posibles valores de un enum. En el caso de una coincidencia, debería devolver la instancia enum; de lo contrario, debería devolver un valor predeterminado.

La función no puede usar internamente try/ catch, lo que excluye el uso Enum.Parse, lo que genera una excepción cuando se le da un argumento no válido.

Me gustaría usar algo parecido a una TryParsefunción para implementar esto:

public static TEnum ToEnum<TEnum>(this string strEnumValue, TEnum defaultValue)
{
   object enumValue;
   if (!TryParse (typeof (TEnum), strEnumValue, out enumValue))
   {
       return defaultValue;
   }
   return (TEnum) enumValue;
}
Manish Basantani
fuente
8
No entiendo esta pregunta; estás diciendo "Quiero resolver este problema, pero no quiero usar ninguno de los métodos que me darían una solución". ¿Cuál es el punto de?
Domenic
1
¿Cuál es su aversión a intentar / encontrar una solución? Si está tratando de evitar las excepciones porque son 'costosas', tómese un descanso. En el 99% de los casos, la excepción de costo de lanzamiento / captura es insignificante en comparación con su código principal.
SolutionYogi
1
El costo del manejo de excepciones no es tan malo. Demonios, las implementaciones internas de toda esta conversión de enumeración están llenas de manejo de excepciones. Sin embargo, realmente no me gustan las excepciones que se lanzan y capturan durante la lógica de aplicación normal. A veces puede ser útil interrumpir todas las excepciones que se lanzan (incluso cuando se detectan). Lanzar excepciones por todas partes hará que sea mucho más molesto de usar :)
Thorarin
3
@Domenic: Solo estoy buscando una solución mejor que la que ya conozco. ¿Alguna vez acudiría a una consulta ferroviaria para solicitar una ruta o tren que ya conoce :)?
Manish Basantani
2
@Amby, el costo de simplemente ingresar a un bloque try / catch es insignificante. El costo de LANZAR una excepción no lo es, pero se supone que es excepcional, ¿no? Además, no digas "nunca lo sabemos" ... perfila el código y descúbrelo. No pierdas el tiempo preguntándote si algo va lento, ¡DESCÚBRELO!
Akmad

Respuestas:

31

Como han dicho otros, tienes que implementar el tuyo propio TryParse. Simon Mourier está proporcionando una implementación completa que se encarga de todo.

Si está utilizando enumeraciones de campo de bits (es decir, banderas), también debe manejar una cadena como la "MyEnum.Val1|MyEnum.Val2"que es una combinación de dos valores de enumeración. Si solo llama Enum.IsDefinedcon esta cadena, devolverá falso, aunque lo Enum.Parsemaneje correctamente.

Actualizar

Como lo mencionaron Lisa y Christian en los comentarios, Enum.TryParseahora está disponible para C # en .NET4 y versiones posteriores.

Documentos de MSDN

Victor Arndt Mueller
fuente
Quizás el menos sexy, pero estoy de acuerdo en que este es definitivamente el mejor hasta que su código se migre a .NET 4.
Lisa
1
Como se menciona a continuación, pero no realmente visible: A partir de .Net 4 Enum.TryParse está disponible y funciona sin codificación adicional. Más información está disponible en MSDN: msdn.microsoft.com/library/vstudio/dd991317%28v=vs.100%29.aspx
Christian
106

Enum.IsDefined hará las cosas. Puede que no sea tan eficiente como probablemente sería TryParse, pero funcionará sin manejo de excepciones.

public static TEnum ToEnum<TEnum>(this string strEnumValue, TEnum defaultValue)
{
    if (!Enum.IsDefined(typeof(TEnum), strEnumValue))
        return defaultValue;

    return (TEnum)Enum.Parse(typeof(TEnum), strEnumValue);
}

Cabe destacar: un TryParse método se añadió en .NET 4.0.

Thorarin
fuente
1
Mejor respuesta que he visto hasta ahora ... no try / catch, no hay GetNames :)
Thomas Levesque
13
Inconvenientes de Enum.IsDefined: blogs.msdn.com/brada/archive/2003/11/29/50903.aspx
Nader Shirazie
6
también no hay caso de ignorar en IsDefined
Anthony Johnston
2
@Anthony: si desea admitir la insensibilidad a mayúsculas y minúsculas, necesitará GetNames. Internamente, todos estos métodos (incluidos Parse) utilizan GetHashEntry, que hace la reflexión real, una vez. En el lado positivo, .NET 4.0 tiene un TryParse, y también es genérico :)
Thorarin
+1 ¡Me salvó el día! Estoy respaldando un montón de código de .NET 4 a .NET 3.5 y me salvaste :)
daitangio
20

Aquí hay una implementación personalizada de EnumTryParse. A diferencia de otras implementaciones comunes, también admite la enumeración marcada con el Flagsatributo.

    /// <summary>
    /// Converts the string representation of an enum to its Enum equivalent value. A return value indicates whether the operation succeeded.
    /// This method does not rely on Enum.Parse and therefore will never raise any first or second chance exception.
    /// </summary>
    /// <param name="type">The enum target type. May not be null.</param>
    /// <param name="input">The input text. May be null.</param>
    /// <param name="value">When this method returns, contains Enum equivalent value to the enum contained in input, if the conversion succeeded.</param>
    /// <returns>
    /// true if s was converted successfully; otherwise, false.
    /// </returns>
    public static bool EnumTryParse(Type type, string input, out object value)
    {
        if (type == null)
            throw new ArgumentNullException("type");

        if (!type.IsEnum)
            throw new ArgumentException(null, "type");

        if (input == null)
        {
            value = Activator.CreateInstance(type);
            return false;
        }

        input = input.Trim();
        if (input.Length == 0)
        {
            value = Activator.CreateInstance(type);
            return false;
        }

        string[] names = Enum.GetNames(type);
        if (names.Length == 0)
        {
            value = Activator.CreateInstance(type);
            return false;
        }

        Type underlyingType = Enum.GetUnderlyingType(type);
        Array values = Enum.GetValues(type);
        // some enums like System.CodeDom.MemberAttributes *are* flags but are not declared with Flags...
        if ((!type.IsDefined(typeof(FlagsAttribute), true)) && (input.IndexOfAny(_enumSeperators) < 0))
            return EnumToObject(type, underlyingType, names, values, input, out value);

        // multi value enum
        string[] tokens = input.Split(_enumSeperators, StringSplitOptions.RemoveEmptyEntries);
        if (tokens.Length == 0)
        {
            value = Activator.CreateInstance(type);
            return false;
        }

        ulong ul = 0;
        foreach (string tok in tokens)
        {
            string token = tok.Trim(); // NOTE: we don't consider empty tokens as errors
            if (token.Length == 0)
                continue;

            object tokenValue;
            if (!EnumToObject(type, underlyingType, names, values, token, out tokenValue))
            {
                value = Activator.CreateInstance(type);
                return false;
            }

            ulong tokenUl;
            switch (Convert.GetTypeCode(tokenValue))
            {
                case TypeCode.Int16:
                case TypeCode.Int32:
                case TypeCode.Int64:
                case TypeCode.SByte:
                    tokenUl = (ulong)Convert.ToInt64(tokenValue, CultureInfo.InvariantCulture);
                    break;

                //case TypeCode.Byte:
                //case TypeCode.UInt16:
                //case TypeCode.UInt32:
                //case TypeCode.UInt64:
                default:
                    tokenUl = Convert.ToUInt64(tokenValue, CultureInfo.InvariantCulture);
                    break;
            }

            ul |= tokenUl;
        }
        value = Enum.ToObject(type, ul);
        return true;
    }

    private static char[] _enumSeperators = new char[] { ',', ';', '+', '|', ' ' };

    private static object EnumToObject(Type underlyingType, string input)
    {
        if (underlyingType == typeof(int))
        {
            int s;
            if (int.TryParse(input, out s))
                return s;
        }

        if (underlyingType == typeof(uint))
        {
            uint s;
            if (uint.TryParse(input, out s))
                return s;
        }

        if (underlyingType == typeof(ulong))
        {
            ulong s;
            if (ulong.TryParse(input, out s))
                return s;
        }

        if (underlyingType == typeof(long))
        {
            long s;
            if (long.TryParse(input, out s))
                return s;
        }

        if (underlyingType == typeof(short))
        {
            short s;
            if (short.TryParse(input, out s))
                return s;
        }

        if (underlyingType == typeof(ushort))
        {
            ushort s;
            if (ushort.TryParse(input, out s))
                return s;
        }

        if (underlyingType == typeof(byte))
        {
            byte s;
            if (byte.TryParse(input, out s))
                return s;
        }

        if (underlyingType == typeof(sbyte))
        {
            sbyte s;
            if (sbyte.TryParse(input, out s))
                return s;
        }

        return null;
    }

    private static bool EnumToObject(Type type, Type underlyingType, string[] names, Array values, string input, out object value)
    {
        for (int i = 0; i < names.Length; i++)
        {
            if (string.Compare(names[i], input, StringComparison.OrdinalIgnoreCase) == 0)
            {
                value = values.GetValue(i);
                return true;
            }
        }

        if ((char.IsDigit(input[0]) || (input[0] == '-')) || (input[0] == '+'))
        {
            object obj = EnumToObject(underlyingType, input);
            if (obj == null)
            {
                value = Activator.CreateInstance(type);
                return false;
            }
            value = obj;
            return true;
        }

        value = Activator.CreateInstance(type);
        return false;
    }
Simón Mourier
fuente
1
proporcionaste la mejor implementación y la he utilizado para mis propios fines; sin embargo, me pregunto por qué usa Activator.CreateInstance(type)para crear el valor de enumeración predeterminado y no Enum.ToObject(type, 0). ¿Solo es cuestión de gustos?
Pierre Arnaud
1
@Pierre - Hmmm ... no, parecía más natural en ese momento :-) ¿Quizás Enum.ToObject es más rápido ya que usa internamente una llamada interna InternalBoxEnum? Nunca verifiqué eso ...
Simon Mourier
2
Como se menciona a continuación, pero no realmente visible: A partir de .Net 4 Enum.TryParse está disponible y funciona sin codificación adicional. Hay más información disponible en MSDN: msdn.microsoft.com/library/vstudio/dd991317%28v=vs.100%29.aspx
Christian
16

Al final, debes implementar esto en torno a Enum.GetNames:

public bool TryParseEnum<T>(string str, bool caseSensitive, out T value) where T : struct {
    // Can't make this a type constraint...
    if (!typeof(T).IsEnum) {
        throw new ArgumentException("Type parameter must be an enum");
    }
    var names = Enum.GetNames(typeof(T));
    value = (Enum.GetValues(typeof(T)) as T[])[0];  // For want of a better default
    foreach (var name in names) {
        if (String.Equals(name, str, caseSensitive ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase)) {
            value = (T)Enum.Parse(typeof(T), name);
            return true;
        }
    }
    return false;
}

Notas adicionales:

  • Enum.TryParseestá incluido en .NET 4. Consulte aquí http://msdn.microsoft.com/library/dd991876(VS.100).aspx
  • Otro enfoque sería envolver directamente la Enum.Parsecaptura de la excepción lanzada cuando falla. Esto podría ser más rápido cuando se encuentra una coincidencia, pero es probable que sea más lento si no. Dependiendo de los datos que esté procesando, esto puede ser o no una mejora neta.

EDITAR: Acabo de ver una mejor implementación en esto, que almacena en caché la información necesaria: http://damieng.com/blog/2010/10/17/enums-better-syntax-improved-performance-and-tryparse-in-net- 3-5

Ricardo
fuente
Iba a sugerir el uso de default (T) para establecer el valor predeterminado. Resulta que esto no funcionaría para todas las enumeraciones. Por ejemplo, si el tipo subyacente de la enumeración era int default (T) siempre devolverá 0, que puede ser válido o no para la enumeración.
Daniel Ballinger
La implementación en el blog de Damieng no admite enumeraciones con el Flagsatributo.
Uwe Keim
9

Basado en .NET 4.5

Código de muestra a continuación

using System;

enum Importance
{
    None,
    Low,
    Medium,
    Critical
}

class Program
{
    static void Main()
    {
    // The input value.
    string value = "Medium";

    // An unitialized variable.
    Importance importance;

    // Call Enum.TryParse method.
    if (Enum.TryParse(value, out importance))
    {
        // We now have an enum type.
        Console.WriteLine(importance == Importance.Medium);
    }
    }
}

Referencia: http://www.dotnetperls.com/enum-parse

Hugo Hilário
fuente
4

Tengo una implementación optimizada que podría usar en UnconstrainedMelody . Efectivamente, solo está almacenando en caché la lista de nombres, pero lo está haciendo de una manera agradable, fuertemente tipada y genéricamente restringida :)

Jon Skeet
fuente
4
enum EnumStatus
{
    NAO_INFORMADO = 0,
    ENCONTRADO = 1,
    BLOQUEADA_PELO_ENTREGADOR = 2,
    DISPOSITIVO_DESABILITADO = 3,
    ERRO_INTERNO = 4,
    AGARDANDO = 5
}

...

if (Enum.TryParse<EnumStatus>(item.status, out status)) {

}
Everson Rafael
fuente
2

Actualmente no hay Enum.TryParse. Esto se solicitó en Connect ( todavía no hay Enum.TryParse ) y obtuvo una respuesta que indica una posible inclusión en el siguiente marco después de .NET 3.5. Tendrá que implementar las soluciones sugeridas por ahora.

Ahmad Mageed
fuente
1

La única forma de evitar el manejo de excepciones es usar el método GetNames (), y todos sabemos que no se debe abusar de las excepciones para la lógica de aplicación común :)

Philippe Leybaert
fuente
1
No es la única forma. Enum.IsDefined (..) evitará que se produzcan excepciones en el código de usuario.
Thorarin
1

¿Se permite el almacenamiento en caché de una función / diccionario generado dinámicamente?

Debido a que no (parece) conocer el tipo de enumeración de antemano, la primera ejecución podría generar algo que las ejecuciones posteriores podrían aprovechar.

Incluso podría almacenar en caché el resultado de Enum.GetNames ()

¿Estás intentando optimizar para CPU o memoria? ¿De verdad lo necesitas?

Nader Shirazie
fuente
La idea es optimizar la CPU. Estoy de acuerdo en que puedo hacerlo a costa de la memoria. Pero no es la solución que estoy buscando. Gracias.
Manish Basantani
0

Como ya han dicho otros, si no usa Try & Catch, necesita usar IsDefined o GetNames ... Aquí hay algunos ejemplos ... básicamente son todos iguales, el primero que maneja enumeraciones que aceptan valores NULL. Prefiero el segundo ya que es una extensión de cadenas, no enumeraciones ... ¡pero puedes mezclarlas como quieras!

  • www.objectreference.net/post/Enum-TryParse-Extension-Method.aspx
  • flatlinerdoa.spaces.live.com/blog/cns!17124D03A9A052B0!605.entry
  • mironabramson.com/blog/post/2008/03/Another-version-for-the-missing-method-EnumTryParse.aspx
  • lazyloading.blogspot.com/2008/04/enumtryparse-with-net-35-extension.html

fuente
0

No hay un TryParse porque el tipo de Enum no se conoce hasta el tiempo de ejecución. Un TryParse que sigue la misma metodología que, por ejemplo, el método Date. TryParse arrojaría un error de conversión implícito en el parámetro ByRef.

Sugiero hacer algo como esto:

//1 line call to get value
MyEnums enumValue = (Sections)EnumValue(typeof(Sections), myEnumTextValue, MyEnums.SomeEnumDefault);

//Put this somewhere where you can reuse
public static object EnumValue(System.Type enumType, string value, object NotDefinedReplacement)
{
    if (Enum.IsDefined(enumType, value)) {
        return Enum.Parse(enumType, value);
    } else {
        return Enum.Parse(enumType, NotDefinedReplacement);
    }
}
ben
fuente
Para los Trymétodos cuyos resultados pueden ser tipos de valor, o donde nullpuede ser un resultado legítimo (por ejemplo, el Dictionary.TryGetValue, which has both such traits), the normal pattern is for a método Try` para devolver booly pasar el resultado como un outparámetro. Para aquellos que devuelven tipos de clase donde nullno es un resultado válido, no hay dificultad en usar un nullretorno para indicar falla.
supercat
-1

Eche un vistazo a la clase Enum (¿estructura?). Hay un método Parse sobre eso, pero no estoy seguro de un tryparse.

Spence
fuente
Sé sobre el método Enum.Parse (typeof (TEnum), strEnumValue). Lanza ArgumentException si strEnumValue no es válido. Buscando TryParse ........
Manish Basantani
-2

Este método convertirá un tipo de enumeración:

  public static TEnum ToEnum<TEnum>(object EnumValue, TEnum defaultValue)
    {
        if (!Enum.IsDefined(typeof(TEnum), EnumValue))
        {
            Type enumType = Enum.GetUnderlyingType(typeof(TEnum));
            if ( EnumValue.GetType() == enumType )
            {
                string name = Enum.GetName(typeof(HLink.ViewModels.ClaimHeaderViewModel.ClaimStatus), EnumValue);
                if( name != null)
                    return (TEnum)Enum.Parse(typeof(TEnum), name);
                return defaultValue;
            }
        }
        return (TEnum)Enum.Parse(typeof(TEnum), EnumValue.ToString());
    } 

Comprueba el tipo subyacente y obtiene el nombre para analizarlo. Si todo falla, devolverá el valor predeterminado.

Naveed Ahmed
fuente
3
¿Qué está haciendo esto? "Enum.GetName (typeof (HLink.ViewModels.ClaimHeaderViewModel.ClaimStatus), EnumValue)" Probablemente alguna dependencia de su código local.
Manish Basantani