2013-02-22 72 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)