2013-02-22 33 views

Trả lời

6

Mất nhiều thời gian hơn tôi hy vọng nhận được quyền này, nhưng nó hoạt động và đã được thử nghiệm. Hy vọng điều này tiết kiệm một ai đó một thời gian!

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

Bạn có thể thay đổi các câu lệnh chuyển đổi đó thành sự phản chiếu thay vì liệt kê tất cả các loại có thể và rút ngắn mã. –

+0

@NickTurner: hiệu suất là quan trọng đối với phương pháp này, phản ánh không tốt cho hiệu suất. –

+0

lưu ý: 'Enum.Tryparse' trong .net 4+ sẽ không ném các ngoại lệ này. https://msdn.microsoft.com/en-us/library/dd991317%28v=vs.110%29.aspx – Julian

3

Nó sẽ không thể là một phương pháp tĩnh trên Enum (phương pháp khuyến nông tĩnh hoàn toàn không có ý nghĩa), nhưng nó phải làm việc

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

tôi không thích sử dụng một try-catch để xử lý bất kỳ sự cố chuyển đổi hoặc các sự kiện không đặc biệt khác như là một phần của quy trình bình thường của đơn đăng ký của tôi, do đó phương pháp Enum.TryParse của riêng mình cho .NET 3.5 và trước đó sử dụng phương pháp Enum.IsDefined() để đảm bảo rằng sẽ không có ngoại lệ do Enum.Parse() ném ra. . Bạn cũng có thể bao gồm một số kiểm tra null trên value để ngăn chặn một ArgumentNullException nếu giá trị là null.

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

Rõ ràng phương pháp này sẽ không nằm trong lớp Enum vì vậy bạn sẽ cần một lớp để bao gồm điều này trong đó sẽ phù hợp.

Một hạn chế là thiếu ràng buộc enum đối với các phương pháp chung, vì vậy bạn sẽ phải xem xét cách bạn muốn xử lý các loại không chính xác. Enum.IsDefined sẽ ném ArgumentException nếu TEnum không phải là enum nhưng tùy chọn duy nhất khác là kiểm tra thời gian chạy và ném một ngoại lệ khác, vì vậy tôi thường không thêm séc bổ sung và chỉ cho phép loại kiểm tra trong các phương pháp này xử lý cho tôi. Tôi muốn xem xét việc thêm IConvertible làm hạn chế khác, chỉ để giúp hạn chế loại hơn nữa.

+0

Tôi thích cái này tốt hơn so với tôi phương pháp tiếp cận (bây giờ đã xóa) cho chắc chắn. –

+0

+1 Đồng ý, nếu tôi đã dành một phút nữa để xem xét các phương pháp Enum và xem phương pháp IsDefined, điều này rất có thể sẽ xảy ra. :) –

+1

Cảm ơn câu trả lời của bạn. Trong khi đó là một khởi đầu tốt, có một vài cân nhắc khác để làm cho nó ngang bằng với thực hiện .NET 4 (ví dụ giá trị như các cờ được phân tách bằng dấu phẩy, giá trị là số, kiểu số enum) –

1

Tại NLog chúng tôi cũng cần Enum.TryParse cho Net 3.5. Chúng tôi đã thực hiện các tính năng cơ bản (chỉ phân tích cú pháp, phân biệt chữ hoa chữ thường và không nhạy cảm, không có cờ) bị ảnh hưởng bởi bài đăng này.

Việc triển khai cơ bản này được kiểm tra đơn vị cao nên có hành vi tương tự như triển khai Microsoft .Net 4.

Mã này có thể được tìm thấy tại the NLog GitHub, và cũng là unit tests are on GitHub (xUnit)

sử dụng (Tất cả các phiên bản Net) - cùng một chữ ký như Net 4,0

EnumHelpers.TryParse(value, true, out parsedValue) //case insensitive 
//or 
EnumHelpers.TryParse(value, out parsedValue) 
Các vấn đề liên quan