2013-02-22 25 views

回答

6

花了超過我希望得到這個權利,但它的工作原理和經過測試。希望這能節省一些時間!

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

您可以將這些開關語句更改爲反射,而不是列出所有可能的類型並縮短代碼。 – 2014-09-24 14:00:04

+0

@NickTurner:性能對於這種方法很重要,反射對性能不好。 – 2014-09-26 09:36:36

+0

注意:.net 4+中的Enum.Tryparse不會拋出這些異常。 https://msdn.microsoft.com/en-us/library/dd991317%28v=vs.110%29.aspx – Julian 2015-02-06 19:12:35

3

這不會是在枚舉一個靜態方法(靜態擴展方法不太意義),但它應該工作

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

我不喜歡使用try-catch處理任何轉換失敗或其他非常規事件作爲我的應用程序正常流程的一部分,因此我自己的.NET Framework 3.5及更早版本的Enum.TryParse方法使用Enum.IsDefined()方法確保不會有Enum.Parse()引發的異常。 。如果值爲空,您還可以在value上包含一些空檢查以防止ArgumentNullException

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

顯然,這種方法不會駐留在Enum類,所以你需要一個類來包括這將是適當的。

一個限制是對泛型方法缺少enum約束,所以您將不得不考慮如何處理不正確的類型。 Enum.IsDefined將拋出一個ArgumentException如果TEnum不是enum但唯一的其他選擇是運行時檢查並拋出一個不同的異常,所以我通常不會添加額外的檢查,只是讓這些方法中的類型檢查處理我。我會考慮增加IConvertible作爲另一個約束,只是爲了更好地限制類型。

+0

我喜歡這個比我更好方法(現在刪除)肯定。 – 2013-02-22 05:26:55

+0

+1同意,如果我再花一分鐘看Enum方法並看到IsDefined方法,這很可能會發生什麼。 :) – 2013-02-22 05:29:20

+1

感謝您的回答。雖然這是一個好的開始,但還有其他一些考慮因素可以使它與.NET 4的實現保持一致(例如,值爲逗號分隔標誌,值爲數字,枚舉數字類型) – 2013-02-22 12:48:44

1

NLog我們還需要Enum.TryParse對於.NET 3.5。我們已經實現了受這篇文章影響的基本功能(只是解析,區分大小寫和不敏感,沒有標誌)。

這個基本的實現經過了高度的單元測試,所以它的行爲與Microsoft的.Net 4實現相同。

的代碼可以在the NLog GitHub發現,也是unit tests are on GitHub(的xUnit)

用法(所有的.NET版本) - 相同的簽名.NET 4.0

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