2009-07-04 27 views
83

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

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

me gustaría usar algo en la línea de una función TryParse para implementar esta:

public static TEnum ToEnum<TEnum>(this string strEnumValue, TEnum defaultValue) 
{ 
    object enumValue; 
    if (!TryParse (typeof (TEnum), strEnumValue, out enumValue)) 
    { 
     return defaultValue; 
    } 
    return (TEnum) enumValue; 
} 
+7

No entiendo esta pregunta; usted está 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 probar/atrapar solución? Si está tratando de evitar la Excepción porque son 'costosos', dese un descanso. En el 99% de los casos, la excepción Costo de arrojar/capturar costo 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. No me gustan las excepciones lanzadas y atrapadas durante la lógica de aplicación normal. A veces puede ser útil romper todas las excepciones que se lanzan (incluso cuando están atrapadas). Lanzar excepciones por todos lados hará que sea mucho más molesto usar :) – Thorarin

Respuesta

27

Como han dicho otros, debe implementar su propio TryParse. Simon Mourier proporciona una implementación completa que se ocupa de todo.

Si está utilizando enums de bitfield (es decir, flags), también debe manejar una cadena como "MyEnum.Val1|MyEnum.Val2" que es una combinación de dos valores enum. Si solo llama al Enum.IsDefined con esta cadena, devolverá falso, aunque Enum.Parse lo maneje correctamente.

actualización

Como se ha mencionado por Lisa y Christian en los comentarios, Enum.TryParse ya está disponible para C# en .NET4 y hacia arriba.

MSDN Docs

+0

Quizás el menos sexy, pero estoy de acuerdo 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 . Hay más información disponible en MSDN: http://msdn.microsoft.com/library/vstudio/dd991317%28v=vs.100%29.aspx – Christian

-1

Tener un vistazo a la clase Enum (struct?) Sí. Hay un método Parse sobre eso, pero no estoy seguro acerca de un tryparse.

+0

Conozco el método Enum.Parse (typeof (TEnum), strEnumValue). Lanza ArgumentException si strEnumValue no es válido. Buscando TryParse ........ –

1

La única manera de evitar el manejo de excepciones es utilizar el método de GetNames(), y todos sabemos que las excepciones no deben ser objeto de abuso para la lógica de aplicación común :)

+1

No es la * única * forma. Enum.IsDefined (..) evitará que se generen excepciones en el código de usuario. – Thorarin

2

No tenemos fuera de la caja de enumeración. TryParse. Esto se solicitó en Connect (Still no Enum.TryParse) y obtuvo una respuesta que indicaba la posible inclusión en el siguiente marco después de .NET 3.5. Tendrá que implementar las soluciones sugeridas por ahora.

1

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

Porque no conoce (parece conocer) el tipo de enumeración antes de tiempo, la primera ejecución podría generar algo que las ejecuciones subsiguientes podrían aprovechar.

Incluso se puede almacenar en caché el resultado de Enum.GetNames()

¿Estás tratando de optimizar para la CPU o la memoria? ¿Tiene Realmente necesita?

+0

La idea es optimizar la CPU. Estoy de acuerdo en que puedo hacerlo en la memoria de costos. Pero no es la solución que estoy buscando. Gracias. –

16

Al final, usted tiene que poner en práctica este alrededor 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.TryParse se incluye en .NET 4. Consulte aquí http://msdn.microsoft.com/library/dd991876(VS.100).aspx
  • Otro enfoque sería Envuelva directamente Enum.Parse capturando 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 lo es. Dependiendo de los datos que está procesando, esto puede o no ser una mejora neta.

EDIT: Sólo se ve una mejor aplicación de la presente, que se 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

+0

Iba a sugerir usar el valor predeterminado (T) para establecer el valor predeterminado. Resulta que esto no funcionaría para todas las enumeraciones. P.ej. Si el tipo subyacente de la enumeración fue int por defecto (T) siempre devolverá 0, que puede o no ser válido para la enumeración. –

+0

La implementación en el blog de Damieng _no_ admite enums con el atributo 'Flags'. –

97

Enum.IsDefined va a hacer las cosas. Puede que no sea tan eficiente como podría ser un TryParse, pero funcionará sin ningún tipo de manejo.

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

A tener en cuenta: un método TryParse se añadió en .NET 4.0.

+1

La mejor respuesta que he visto hasta ahora ... no try/catch, no GetNames :) –

+13

Desventajas de Enum.IsDefined: http://blogs.msdn.com/brada/archive/2003/11/29/50903. aspx –

+1

GetNames() tiene los mismos inconvenientes ... – Thorarin

0

Como otros ya se ha dicho, si no se utiliza Trate & captura, es necesario utilizar IsDefined o GetNames ... He aquí algunos ejemplos ... que básicamente son todos iguales, la primera uno manejando enumeraciones que aceptan nulos. Prefiero el segundo porque 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/poste/2008/03/Otro-versión-de-la-falta-método-EnumTryParse.aspx
  • lazyloading.blogspot.com/2008/04/enumtryparse-with-net-35-extension.html
4

Tengo una implementación optimizada que puede usar en UnconstrainedMelody. Efectivamente es sólo el almacenamiento en caché la lista de nombres, pero es hacerlo en un buen establecimiento inflexible manera, genéricamente constreñido :)

-2

Este método será 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()); 
    } 

Se comprueba el tipo subyacente y obtener el nombre en contra de él para analizar. Si todo falla, devolverá el valor predeterminado.

+3

¿Qué está haciendo esto? "Enum.GetName (typeof (HLink.ViewModels.ClaimHeaderViewModel.ClaimStatus), EnumValue)" Probablemente dependa de su código local. –

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 el método Date.TryParse arrojaría un error de conversión implícita 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); 
    } 
} 
+0

Para los métodos 'Try' cuyos resultados pueden ser tipos de valor, o donde' null' puede ser un resultado legítimo (p. Ej. 'Dictionary.TryGetValue, que tiene ambos rasgos), el patrón normal es que un método' Try' devuelva 'bool ', y pasa el resultado como un parámetro' out'. Para aquellos que devuelven tipos de clases donde 'null' no es un resultado válido, no hay dificultad usando un retorno' nulo' para indicar fallas. – supercat

19

Aquí es una implementación personalizada de EnumTryParse. A diferencia de otras implementaciones comunes, también admite la enumeración con el atributo Flags.

/// <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; 
    } 
+0

proporcionó la mejor implementación y la he usado para mis propios fines; sin embargo, me pregunto por qué usas 'Activator.CreateInstance (type)' para crear el valor enum predeterminado y no 'Enum.ToObject (type, 0)'. ¿Solo una cuestión de gusto? –

+0

@Pierre - Hmmm ... no, parecía más natural en ese momento :-) Tal vez Enum.ToObject es más rápido ya que internamente está usando una llamada interna InternalBoxEnum? Nunca lo comprobé ... –

+0

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: http://msdn.microsoft.com/library/vstudio/dd991317%28v=vs.100%29.aspx – Christian

6

Basado en .NET 4.5

código de ejemplo siguiente

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

1
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)) { 

}

Cuestiones relacionadas