2012-02-01 106 views
2

我開發周圍的TryParse一個通用的包裝,如下:如何編寫通用擴展方法?

public delegate bool ParseDelegate<T>(string s, out T result); 

    public static T? ParseOrNull<T>(this string value, ParseDelegate<T> parse) where T : struct 
    { 
     T result; 
     var parsed = parse(value, out result); 
     return parsed ? result : (T?)null; 
    } 

    [Test] 
    public void ParsesValidInt() 
    { 
     Assert.AreEqual(1234, "1234".ParseOrNull<int>(int.TryParse)); 
    } 

    [Test] 
    public void ParsesValidDecimal() 
    { 
     Assert.AreEqual(12.34M, "12.34".ParseOrNull<decimal>(decimal.TryParse)); 
    } 

這是有點重複。有沒有辦法避免提及int.TryParse不惜一切,讓我的電話如下所示:

"1234".ParseOrNull<int>() 
+3

您是否嘗試確定泛型參數「T」的類型,然後使用適當的解析方法來代替傳入委託的類型? – Bernard 2012-02-01 16:44:06

回答

6

有沒有辦法避免int.TryParse提的好,所以,我的電話如下所示:

不是直接的,因爲TryParse不是共享接口的一部分。如果有這些值類型的共享接口,則可以通過約束來實現。


就個人而言,我不會建議使用擴展方法。我寧願寫爲更多的東西一樣:

public static class Parse 
{ 
    public delegate bool ParseDelegate<T>(string s, out T result); 
    public static T? FromString<T>(string value, ParseDelegate<T> parse) where T : struct 
    { 
     T result; 
     var parsed = parse(value, out result); 
     return parsed ? result : (T?)null; 
    } 
    public static int? ToNullableInt32(string value) 
    { 
     return FromString<int>(value, int.TryParse); 
    } 
    public static double? ToNullableDouble(string value) 
    { 
     return FromString<double>(value, double.TryParse); 
    } 
} 

這增添了幾分的開銷了前面,但可以讓你非常乾淨地寫這些,即:

int? first = Parse.FromString<int>("1234", int.TryParse); 
    int? second = Parse.ToNullableInt32("1234"); 
    double? third = Parse.ToNullableDouble("1234"); 

我看到把價值不大一種擴展方法,特別是在像string(隨處使用)之類的東西,因爲它「污染」了字符串本身的編譯。你會在使用字符串的任何地方看到這一點 - 基本上,任何時候使用這個命名空間,最終都會在智能感知中使用這些解析方法等。另外,這看起來更像是一種「效用」,而不是應該出現的東西作爲字符串本身的內置功能,這就是爲什麼我個人更喜歡單獨的類。

+0

我喜歡你的建議,以及不要擴展字符串的建議。謝謝! – 2012-02-01 17:07:06

3

總之沒有,但你可以添加一個新的helper方法:

public static int? ParseInt(this string value) 
{ 
    return value.ParseOrNull<int>(int.TryParse); 
} 

然後:

"1234".ParseInt(); 
1

答案是YES大。你正在試圖利用你正在轉換的類型的靜態T.TryParse(string,out T)函數的存在,而且我們可以通過一點思考來做到這一點。

public static T? ParseOrNull<T>(this string str) 
    where T: struct, IConvertible 
{ 
    // find the TryParse method. 
    var parseMethod = typeof(T).GetMethod("TryParse", 
            // We want the public static one 
            BindingFlags.Public | BindingFlags.Static, 
            Type.DefaultBinder, 
            // where the arguments are (string, out T) 
            new[] { typeof(string), typeof(T).MakeByRefType() }, 
            null); 
    if (parseMethod == null) 
     // You need to know this so you can parse manually 
     throw new InvalidOperationException(
          string.Format("{0} doesn't have a TryParse(..) function!", 
                typeof(T).FullName)); 
    // create the parameter list for the function call 
    var args = new object[] { str, default(T) }; 
    // and then call the function. 
    if ((bool)parseMethod.Invoke(null, args)) 
     return (T?)args[1]; // if it returned true 
    // if it returned false 
    return null; 
} 

這是我提供的原始答案的基礎上,你需要兩個不同的解析方法的思路:一爲值類型,另一個是引用類型。

public delegate bool ParseDelegate<T>(string s, out T result); 
    public static T? ParseOrNull<T>(this string str, ParseDelegate<T> Parse) 
     where T: struct 
    { 
     T result; 
     if (!Parse(str, out result)) 
      return null; 
     return result; 
    } 

    public static T ParseOrNull<T>(this string str, ParseDelegate<T> Parse) 
     where T : class 
    { 
     T result; 
     if (!Parse(str, out result)) 
      return null; 
     return result; 
    } 
2

看看微軟如何處理幾種類型。他們爲每種類型提供了一種方法。 Enumerable.Sum Method就是一個很好的例子。如果你想簡化調用代碼,你應該爲每個類型提供了重載:

public static int? ParseOrNull<int>(this string value) 
{ 
    int result; 
    var parsed = int.TryParse(value, out result); 
    return parsed ? result : (T?)null; 
} 
public static long? ParseOrNull<long>(this string value) 
{ 
    long result; 
    var parsed = long.TryParse(value, out result); 
    return parsed ? result : (T?)null; 
} 
// same for ulong, long, uint, ushort, short, byte, 
// bool, float, double, decimal. Do I forget one ? 

我想簡化比方法本身調用它更重要。事實上,處理的數量並不是很多。

+0

注意如果你想避免在這個類中重複的代碼,你也可以通過OP的原始方法實現所有的特定版本...... – 2012-02-01 16:53:08

+0

從純粹的性能角度來看,我更喜歡重複這個邏輯十次以避免委託調用。確實邏輯是多餘的,但這是一個非常小的冗餘,並且很好地隔離(在一個擴展類中)。 – 2012-02-01 16:57:37

+0

儘管我同意委託開銷的性能成本非常低,但我認爲這是一個過早的優化。解析字符串的成本比委託調用的附加成本要高很多,因此額外的可維護性將超過性能。如果沒有測量的,已知的理由來優化每種類型的話,那麼它是有益的。 – 2012-02-01 16:59:35

1

,你可以使用Convert.ChangeType

public static T? ParseOrNull<T>(this string value) where T : struct, IConvertible 
{ 
    try 
    {   
     return (T)Convert.ChangeType(value, typeof(T)); 
    } 
    catch (FormatException ex) 
    { 
     return null; 
    } 
} 

它不會有相同的良好性能(使用嘗試捕捉)爲TryParse,而應針對所有IConvertible工種

+0

+1我會在其他時間使用這個技巧;) – 2012-02-01 17:07:52

0
public static T? ParseOrNull<T>(this string value) 
      where T : struct 
     { 
      T result = default(T); 

      object[] parameters = new object[] { value, result }; 
      foreach (System.Reflection.MethodInfo method in 
       typeof(T).GetMethods() 
       .Where(method => method.Name == "TryParse") 
       .Where(method => method.GetParameters().Length == 2) //as opposed to the 4 argument version 
       .Take(1) //shouldn't be needed, but just in case 
       ) 
      { 
       method.Invoke(null, parameters); 
      } 

      return (T)parameters[1]; 
     } 

裏德提到,我寧願不使用字符串的擴展方法。我只是使用Parser.Parse(字符串值)。很容易修復,只需刪除'this'即可。

+0

如果你使用反射解決方案,你至少應該在類型爲key的靜態字典中緩存MethodInfo。調用仍然可能比直接使用'TryParse'慢得多。 – Magnus 2012-02-01 18:32:20

+0

我也在考慮這個問題,但我想確保OP在關注優化之前對這個概念感興趣。也可以在速度很重要的情況下直接使用相關的TryParse,並且對於頻繁使用頻率不足以關心額外毫秒的情況使用上述方法。 – Servy 2012-02-01 18:37:55