2009-12-10 133 views
8

說我有一個方法,接受一個int作爲一個字符串,如果解析成功返回int,否則返回一個空值。泛型和可空類型

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

該方法如何重寫,以便它不僅可以與int ?,還可以使用long?,decimal?和DateTime? ?

回答

14

這很有趣,你應該提到它,因爲我的東西瞎搞就這樣的一天:

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

這是一個類似的做法,但認爲這是一個更好的TryParse方法返回一個Tuple<Boolean, T?>(這需要.NET 4)。元組的第一個屬性是一個布爾值,指示解析嘗試的成功或失敗,第二個屬性是類型爲通用類型參數的可爲空的值,如果解析失敗,則爲null,解析成功時爲值。

它通過使用反射來檢索泛型類型參數的靜態Parse(String)方法,並調用該方法對於在我建立它作爲一個擴展的方法來讓你做這樣的東西傳遞的字符串:

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

不幸的是這不會對enums工作,因爲他們不具備解析方法相同的簽名,所以你不能使用這個擴展來解析enum,但它不會是很難破解這個高達爲枚舉做一個特例。

這種方法的好處之一是通過反射檢索Parse方法的成本只會在第一次使用時產生,因爲爲所有後續使用創建靜態委託。


一兩件事 - 的唯一的事情就是笨重對這種做法是沒有任何語言擴展或語法糖,這將使這個易於使用。我希望通過此代碼實現的是使用BCL中存在的標準TryParse方法的笨重方法。

我個人認爲,這一模式比較難看:

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

主要是因爲它需要的時間提前一個變量聲明和使用out參數。以上我的做法是不是真的那麼好很多:

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

什麼是真的很酷將看到一個建有Tuple<Boolean, T?>的工作,使我們與這種類型工作的順利開展一個C#語言的擴展,但我越感覺到,我寫這篇文章似乎並不可行。

+1

假設這真的作品,+1:P – 2009-12-10 05:00:47

+0

考慮的'TryParse'沒有投擲失敗的異常,存在的主要目的,你的「好辦法」已撤消其存在的目的:HTTP: //www.codinghorror.com/blog/archives/000358.html – 2009-12-10 05:25:32

+0

@ 280z28 - 夠公平,但我拋出的例外情況不同。當你試圖解析一個沒有'TryParse(String)'方法的類型時,就會拋出這個異常,當你調用一個普通的'TryParse'方法時,這種方法從來不會發生。這個異常無疑會被開發人員在測試中發現,並且不會在運行時發生,因此它與被解析失敗掩蓋的異常不完全相同。 – 2009-12-10 05:34:52

3

而不是使用問號,你可以明確地使用可空關鍵字: 例如,

int?等於Nullable<int>

因此切換你的原創設計Nullable<T> ParseValue(string valueAsString)應該做的伎倆:只是做之後的通用實現。

2

如果您可以等待C#4.0,則可以使用dynamic關鍵字來解決此類情況。

0

這些列出的類型都有一個名爲TryParse的靜態方法。它們看起來很相似,但實際上簽名是完全不同的。他們遵循類似的「模式」,但編譯器無法檢測到這種情況。

您可以嘗試做這樣的事情:

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

然而,你不能。編譯器無法知道(在編譯時)如何將類型'T'轉換爲int(或任何其他類型)。

您可以重複投射以完成此項工作。 (感謝,Dotson)

用法:

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

將我先投到對象上: (T)(object)i – 2009-12-10 06:01:33

0

其實,你可以更新什麼馬特的代碼已經完成,並把它,這裏是代碼:

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

而且還可以像這樣使用它: double? i = TryParse(「111.111」); int? a = TryParse(「111」);

3

最好實現一個擴展方法,你甚至可以解析枚舉。這樣,您就可以得到一個可空<ForAnyValueType>這樣的:

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

我真的不明白爲什麼使用安卓解決方案的元組,只要我們反正返回一個空的,好像在做同樣的事情兩次。我編輯他的解決方案使用TryParse並允許返回Nullable或一些默認值指定爲參數。

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