2009-12-10 654 views
8

Giả sử tôi có một phương thức lấy int như một chuỗi và trả về int nếu phân tích cú pháp thành công hoặc một giá trị null khác.Generics và nullable type

int? ParseValue(string intAsString) 
    { 
     int i; 
     if (int.TryParse(intAsString, out i)) 
      return i; 
     return null; 
    } 

Phương pháp này có thể được viết lại sao cho nó hoạt động không chỉ với int ?, nhưng cũng dài ?, thập phân? và DateTime? ?

Trả lời

14

Thật buồn cười, bạn nên đề cập đến nó bởi vì tôi đã rối tung xung quanh với một cái gì đó như thế này ngày khác:

using System; 
using System.Reflection; 

static class Example 
{ 
    public static Tuple<Boolean, T?> TryParse<T>(this String candidate) 
     where T : struct 
    { 
     T? value = null; 
     Boolean success = false; 

     var parser = ParsingBinder<T>.GetParser(); 

     try 
     { 
       value = parser(candidate); 
       success = true; 
     } 
     catch (FormatException) { } 

     return new Tuple<Boolean,T?>(success, value); 
    } 
} 

static class ParsingBinder<T> 
{ 
    static Func<String, T> parser; 

    public static Func<String, T> GetParser() 
    { 
     if (parser == null) 
       parser = getParser(); 

     return parser; 
    } 

    static Func<String, T> getParser() 
    { 
     MethodInfo methodInfo 
      = typeof(T).GetMethod(
        "Parse", new [] { typeof(String) }); 

     if (methodInfo == null) 
       throw new Exception(
         "Unable to retrieve a \"Parse\" method for type."); 

     return (Func<String, T>)Delegate 
     .CreateDelegate(typeof(Func<String, T>), methodInfo); 
    } 
} 

Đó là một cách tiếp cận tương tự nhưng nghĩ về nó như một TryParse phương pháp tốt hơn mà trả về một Tuple<Boolean, T?> (điều này đòi hỏi .NET 4). Thuộc tính đầu tiên của tuple là giá trị boolean cho biết thành công hay thất bại của việc phân tích cú pháp và thuộc tính thứ hai là một giá trị nullable được gõ vào đối số kiểu chung sẽ là null nếu phân tích cú pháp thất bại và giá trị nếu phân tích thành công.

Nó hoạt động bằng cách sử dụng phản ánh để lấy một Parse(String) phương pháp tĩnh từ đối số kiểu chung chung và gọi phương pháp đó cho chuỗi được thông qua tại tôi đã xây dựng nó như là một phương pháp mở rộng để cho phép bạn để làm công cụ như thế này:.

var intValue = "1234".TryParse<Int32>(); 
var doubleValue = "1234".TryParse<Double>(); 

Thật không may điều này sẽ không hoạt động trên enums vì chúng không có chữ ký giống nhau cho phương pháp phân tích cú pháp, do đó bạn không thể sử dụng tiện ích này để phân tích cú pháp số enum. tạo ra một trường hợp đặc biệt cho enums.

Một trong những điều tốt đẹp về cách tiếp cận này là chi phí truy xuất phương pháp Parse thông qua phản ánh chỉ phát sinh khi sử dụng lần đầu tiên do đại biểu tĩnh được tạo cho tất cả các lần sử dụng tiếp theo.


Một điều nữa - điều duy nhất gây khó chịu về cách tiếp cận này là không có phần mở rộng ngôn ngữ hoặc đường cú pháp giúp việc này trở nên dễ dàng. Những gì tôi đã hy vọng đạt được với mã này là ít hơn cách clunky của việc sử dụng các phương pháp tiêu chuẩn TryParse tồn tại trong BCL.

Cá nhân tôi thấy mô hình này khá xấu xí:

Int32 value; 
if (Int32.TryParse(someString, out value)) 
    // do something with value 

chủ yếu là bởi vì nó đòi hỏi một khai báo biến trước thời hạn và việc sử dụng một tham số out. Cách tiếp cận của tôi ở trên không phải là thực sự là tốt hơn nhiều:

var result = someString.TryParse<Int32>(); 
if (result.Item1) 
    // do something with result.Item2 

gì sẽ thực sự mát mẻ sẽ được nhìn thấy một phần mở rộng ngôn ngữ C# được xây dựng để làm việc với một Tuple<Boolean, T?> mà sẽ cho phép chúng tôi làm việc với loại hình này suôn sẻ nhưng Tôi có cảm giác tôi càng viết về điều này rằng nó không thực sự có vẻ khả thi.

+1

Giả sử điều này thực sự hoạt động, 1: P –

+0

Xét mục đích chính cho sự tồn tại của 'TryParse' không ném một ngoại lệ trên thất bại, "cách tiếp cận tốt hơn" của bạn đã hoàn tác nhằm mục đích tồn tại của nó: http: //www.codinghorror.com/blog/archives/000358.html –

+0

@ 280z28 - Đủ công bằng nhưng ngoại lệ tôi ném là khác nhau. Ngoại lệ này được ném ra khi bạn cố gắng phân tích một kiểu không có phương thức 'TryParse (String)' mà không bao giờ xảy ra khi bạn gọi phương thức 'TryParse' bình thường. Ngoại lệ này chắc chắn sẽ được phát hiện bởi các nhà phát triển trong thử nghiệm và sẽ không xảy ra trong thời gian chạy vì vậy nó không chính xác giống như ngoại lệ sẽ được che giấu bởi một thất bại phân tích cú pháp. –

3

Thay vì sử dụng các dấu hỏi, bạn có thể sử dụng một cách rõ ràng từ khóa Nullable: ví dụ,

int? bằng Nullable<int>

Do đó chuyển đổi thiết kế ban đầu của bạn để Nullable<T> ParseValue(string valueAsString) nên làm các trick: chỉ cần làm thực hiện chung sau này.

2

Nếu bạn có thể đợi C# 4.0, bạn có thể sử dụng từ khóa dynamic để giải quyết tình huống như vậy.

0

Các loại mà bạn đã liệt kê tất cả đều có phương thức tĩnh gọi là TryParse. Chúng trông giống nhau, nhưng trên thực tế các chữ ký hoàn toàn khác nhau. Chúng thực hiện theo một 'mẫu' tương tự, nhưng trình biên dịch không thể phát hiện được.

Bạn có thể cố gắng làm một cái gì đó như thế này:

public T? ParseValue<T>(string value) where T : struct 
    { 
     if (typeof(T) == typeof(int)) 
     { 
      int i; 
      if (int.TryParse(value, out i)) 
       return (T)(object)i; 
      return null; 
     } 
     if (typeof(T) == typeof(decimal)) 
     { 
      decimal d; 
      if (decimal.TryParse(value, out d)) 
       return (T)(object)d; 
      return null; 
     } 
     // other supported types... 
     throw new ArgumentException("Type not supported"); 
    } 

Tuy nhiên, bạn có thể không. Trình biên dịch không có cách nào để biết (tại thời gian biên dịch) làm thế nào để chuyển đổi loại 'T' thành một int (hoặc bất kỳ loại nào khác).

Bạn có thể ghép đôi để thực hiện tác phẩm này. (Cảm ơn, Dotson)

Cách sử dụng:

 var mydecimal = ParseValue<decimal>("12.1"); 
     var myint = ParseValue<int>("-22"); 
     var badint = ParseValue<int>("Bad"); 
     // badint.HasValue == false 
+0

Truyền i đến một đối tượng trước: (T) (đối tượng) i –

0

Trên thực tế, Bạn có thể cập nhật những gì mã của Matt đã làm và làm cho nó, và đây là mã:

enter code here:static T? TryParse<T>(string parse) 
     where T : struct 
    { 
     Type t=typeof(T); 
     if (t==typeof(int)) 
     { 
      int i; 
      if (int.TryParse(parse, out i)) 
       return (T)(object)i; 
      return null; 
      //Console.WriteLine(t.Name); 
     } 
     if (t == typeof(double)) 
     { 
      double i; 
      if (double.TryParse(parse, out i)) 
       return (T)(object)i; 
      return null; 
     } 
     //blabla, more logic like datetime and other data types 
     return null; 
    } 

Và cũng có thể, bạn có thể sử dụng nó như thế này: đôi? i = TryParse ("111.111"); int? a = TryParse ("111");

3

Cách tốt nhất là triển khai phương pháp Extension và thậm chí bạn có thể phân tích các Liệt kê. Bằng cách này bạn có thể nhận được một Nullable <ForAnyValueType> như thế này:

public static T? Parse<T>(this string text) where T: struct 
    { 
     object o = null; 
     try { 
      var ttype = typeof(T); 
      if (ttype.IsEnum) 
      { 
       T n = default(T); 
       if (Enum.TryParse<T>(text, true, out n)) 
        return n; 
      } 
      else 
      o = Convert.ChangeType(text, ttype); 
     } 
     catch { } 

     if (o == null) 
      return new Nullable<T>(); 

     return new Nullable<T>((T)o); 
    } 
1

tôi không thực sự hiểu tại sao sử dụng tuple trong dung dịch Andrews, miễn là chúng ta đang trở về một Nullable dù sao, có vẻ như làm như vậy điều hai lần. Tôi đã chỉnh sửa giải pháp của mình để sử dụng TryParse và cho phép trả lại giá trị Nullable hoặc một số giá trị mặc định được chỉ định làm đối số.

/// <summary> 
    /// 
    /// </summary> 
    /// <typeparam name="T"></typeparam> 
    /// <param name="aText"></param> 
    /// <returns></returns> 
    public static Nullable<T> TryParse<T>(this string aText) where T : struct { 
     T value; 
     if (ParsingBinder<T>.TryParse(aText, out value)) { 
      return value; 
     } 
     return null; 
    } 

    /// <summary> 
    /// 
    /// </summary> 
    /// <typeparam name="T"></typeparam> 
    /// <param name="aText"></param> 
    /// <param name="aDefault"></param> 
    /// <returns></returns> 
    public static T TryParse<T>(this string aText, T aDefault) where T : struct { 
     T value; 
     if (!ParsingBinder<T>.TryParse(aText, out value)) { 
      value = aDefault; 
     } 
     return value; 
    } 

    /// <summary> 
    /// 
    /// </summary> 
    /// <typeparam name="T"></typeparam> 
    static class ParsingBinder<T> where T : struct { 

     /// <summary> 
     /// 
     /// </summary> 
     /// <typeparam name="T"></typeparam> 
     /// <param name="aText"></param> 
     /// <param name="aOutput"></param> 
     /// <returns></returns> 
     public delegate bool Delegate_TryParse<T>(string aText, out T aOutput) where T : struct; 

     /// <summary> 
     /// 
     /// </summary> 
     static Delegate_TryParse<T> methodTryParse; 

     /// <summary> 
     /// 
     /// </summary> 
     /// <returns></returns> 
     public static Delegate_TryParse<T> TryParse { 
      get { 
       if (methodTryParse == null) { 
        methodTryParse = GetParserMethod(); 
       } 
       return methodTryParse; 
      } 
     } 

     /// <summary> 
     /// 
     /// </summary> 
     /// <returns></returns> 
     static Delegate_TryParse<T> GetParserMethod() { 
      var typeT = typeof(T); 
      var paramTypes = new Type[] { typeof(string), typeT.MakeByRefType() }; 
      var methodInfo = typeT.GetMethod("TryParse", BindingFlags.Static | BindingFlags.Public, null, paramTypes, null); 
      if (methodInfo == null) { 
       var message = String.Format("Unable to retrieve a 'TryParse' method for type '{0}'.", typeT.Name); 
       throw new Exception(message); 
      } 
      return (Delegate_TryParse<T>) Delegate.CreateDelegate(typeof(Delegate_TryParse<T>), methodInfo); 
     } 
    }