2013-02-22 20 views

risposta

6

Ho impiegato più tempo di quanto speravo per farlo correttamente, ma funziona ed è stato testato. Spero che questo salvi qualcuno un po 'di tempo!

private static readonly char[] FlagDelimiter = new [] { ',' }; 

    public static bool TryParseEnum<TEnum>(string value, out TEnum result) where TEnum : struct { 
     if (string.IsNullOrEmpty(value)) { 
      result = default(TEnum); 
      return false; 
     } 

     var enumType = typeof(TEnum); 

     if (!enumType.IsEnum) 
      throw new ArgumentException(string.Format("Type '{0}' is not an enum", enumType.FullName)); 


     result = default(TEnum); 

     // Try to parse the value directly 
     if (Enum.IsDefined(enumType, value)) { 
      result = (TEnum)Enum.Parse(enumType, value); 
      return true; 
     } 

     // Get some info on enum 
     var enumValues = Enum.GetValues(enumType); 
     if (enumValues.Length == 0) 
      return false; // probably can't happen as you cant define empty enum? 
     var enumTypeCode = Type.GetTypeCode(enumValues.GetValue(0).GetType()); 

     // Try to parse it as a flag 
     if (value.IndexOf(',') != -1) { 
      if (!Attribute.IsDefined(enumType, typeof(FlagsAttribute))) 
       return false; // value has flags but enum is not flags 

      // todo: cache this for efficiency 
      var enumInfo = new Dictionary<string, object>(); 
      var enumNames = Enum.GetNames(enumType); 
      for (var i = 0; i < enumNames.Length; i++) 
       enumInfo.Add(enumNames[i], enumValues.GetValue(i)); 

      ulong retVal = 0; 
      foreach(var name in value.Split(FlagDelimiter)) { 
       var trimmedName = name.Trim(); 
       if (!enumInfo.ContainsKey(trimmedName)) 
        return false; // Enum has no such flag 

       var enumValueObject = enumInfo[trimmedName]; 
       ulong enumValueLong; 
       switch (enumTypeCode) { 
        case TypeCode.Byte: 
         enumValueLong = (byte)enumValueObject; 
         break; 
        case TypeCode.SByte: 
         enumValueLong = (byte)((sbyte)enumValueObject); 
         break; 
        case TypeCode.Int16: 
         enumValueLong = (ushort)((short)enumValueObject); 
         break; 
        case TypeCode.Int32: 
         enumValueLong = (uint)((int)enumValueObject); 
         break; 
        case TypeCode.Int64: 
         enumValueLong = (ulong)((long)enumValueObject); 
         break; 
        case TypeCode.UInt16: 
         enumValueLong = (ushort)enumValueObject; 
         break; 
        case TypeCode.UInt32: 
         enumValueLong = (uint)enumValueObject; 
         break; 
        case TypeCode.UInt64: 
         enumValueLong = (ulong)enumValueObject; 
         break; 
        default: 
         return false; // should never happen 
       } 
       retVal |= enumValueLong; 
      } 
      result = (TEnum)Enum.ToObject(enumType, retVal); 
      return true; 
     } 

     // the value may be a number, so parse it directly 
     switch (enumTypeCode) { 
      case TypeCode.SByte: 
       sbyte sb; 
       if (!SByte.TryParse(value, out sb)) 
        return false; 
       result = (TEnum)Enum.ToObject(enumType, sb); 
       break; 
      case TypeCode.Byte: 
       byte b; 
       if (!Byte.TryParse(value, out b)) 
        return false; 
       result = (TEnum)Enum.ToObject(enumType, b); 
       break; 
      case TypeCode.Int16: 
       short i16; 
       if (!Int16.TryParse(value, out i16)) 
        return false; 
       result = (TEnum)Enum.ToObject(enumType, i16); 
       break; 
      case TypeCode.UInt16: 
       ushort u16; 
       if (!UInt16.TryParse(value, out u16)) 
        return false; 
       result = (TEnum)Enum.ToObject(enumType, u16); 
       break; 
      case TypeCode.Int32: 
       int i32; 
       if (!Int32.TryParse(value, out i32)) 
        return false; 
       result = (TEnum)Enum.ToObject(enumType, i32); 
       break; 
      case TypeCode.UInt32: 
       uint u32; 
       if (!UInt32.TryParse(value, out u32)) 
        return false; 
       result = (TEnum)Enum.ToObject(enumType, u32); 
       break; 
      case TypeCode.Int64: 
       long i64; 
       if (!Int64.TryParse(value, out i64)) 
        return false; 
       result = (TEnum)Enum.ToObject(enumType, i64); 
       break; 
      case TypeCode.UInt64: 
       ulong u64; 
       if (!UInt64.TryParse(value, out u64)) 
        return false; 
       result = (TEnum)Enum.ToObject(enumType, u64); 
       break; 
      default: 
       return false; // should never happen 
     } 

     return true; 
    } 
+0

Puoi cambiare quelle istruzioni switch in reflection anziché elencando tutti i tipi possibili e abbrevia il codice. –

+0

@NickTurner: le prestazioni sono importanti per questo metodo, la riflessione non è buona per le prestazioni. –

+0

nota: il 'Enum.Tryparse' in .net 4+ non getterà queste eccezioni. https://msdn.microsoft.com/en-us/library/dd991317%28v=vs.110%29.aspx – Julian

3

Non sarà un metodo statico su Enum (metodi di estensione statici non del tutto ha senso), ma dovrebbe funzionare

public static class EnumHelpers 
{ 
    public static bool TryParse<TEnum>(string value, out TEnum result) 
     where TEnum : struct 
    { 
     try 
     { 
      result = (TEnum)Enum.Parse(typeof(TEnum), value); 
     } 
     catch 
     { 
      return false; 
     } 

     return true; 
    } 
} 
14

Non mi piace utilizzare un try-catch per gestire eventuali errori di conversione o altri eventi non eccezionali come parte del normale flusso della mia applicazione, quindi il mio metodo Enum.TryParse per .NET 3.5 e versioni precedenti utilizza il metodo Enum.IsDefined() per assicurarci che non ci sia un'eccezione generata da Enum.Parse() . È anche possibile includere alcuni assegni null su value per impedire un ArgumentNullException se il valore è nullo.

public static bool TryParse<TEnum>(string value, out TEnum result) 
    where TEnum : struct, IConvertible 
{ 
    var retValue = value == null ? 
       false : 
       Enum.IsDefined(typeof(TEnum), value); 
    result = retValue ? 
       (TEnum)Enum.Parse(typeof(TEnum), value) : 
       default(TEnum); 
    return retValue; 
} 

Ovviamente questo metodo non risiederanno nella classe Enum quindi sarà necessario una classe per includere questo in quanto sarebbe opportuno.

Una limitazione è la mancanza di un vincolo enum su metodi generici, quindi è necessario considerare come si desidera gestire i tipi errati. Enum.IsDefined getterà uno 10 se TEnum non è un enum ma l'unica altra opzione è un controllo di runtime e un'eccezione diversa, quindi in genere non aggiungo un controllo aggiuntivo e semplicemente permetto al controllo dei tipi in questi metodi di gestire per me. Prenderò in considerazione l'aggiunta di IConvertible come un altro vincolo, solo per aiutare a limitare ulteriormente il tipo.

+0

mi piace questo uno migliore del mio approccio (ora cancellato) di sicuro. –

+0

+1 Concordato, se avessi passato un altro minuto a esaminare i metodi Enum e visto il metodo IsDefined, è molto probabile che cosa sarebbe successo. :) –

+1

Grazie per la risposta. Sebbene sia stato un buon inizio, ci sono parecchie altre considerazioni per renderlo alla pari con l'implementazione di .NET 4. (ad es. Valore come flag separati da virgole, valore come numero, tipo enum numerico) –

1

A NLog Abbiamo anche bisogno Enum.TryParse per .Net 3.5. Abbiamo implementato le funzionalità di base (solo analisi, maiuscole/minuscole e insensibili, senza bandiere) influenzate da questo post.

Questa implementazione di base è altamente testata in unità, quindi ha lo stesso comportamento dell'implementazione di Net 4 di Microsoft.

Il codice può essere trovato alla the NLog GitHub, e anche il unit tests are on GitHub (xUnit)

Uso (tutte le versioni) - .Net stessa firma Net 4,0

EnumHelpers.TryParse(value, true, out parsedValue) //case insensitive 
//or 
EnumHelpers.TryParse(value, out parsedValue)