2011-10-27 25 views
3

我們從DataRow做了很多打包和解包。是的,我們應該使用ORM,但在此之前,這就是我們所擁有的。作爲這樣的結果,有很多的代碼看起來像這樣:是否有更具有泛型的方式來執行此類行爲?

string username; 

var temp = dr["Username"]; 
if (DbNull.Equals (temp)) 
{ 
    username = "Anonymous"; 
} else { 
    username = dr["Username"].ToString(); 
} 

最終,這成爲一個模式,被翻譯成輔助方法:

string username = StringExtensions.SafeParse (dr["Username"], "Anonymous"); 

這仍然繁瑣,而且所有類型的基元都需要擴展方法。它也混淆了代碼。我在object上創建了一個通用擴展方法,名爲As<T>。用法是這樣的:

string username = dr["Username"].As<string> ("Anonymous"); 

這種相對簡單的變化已經達到泰然自若地與其他開發者,並在地方有很多的習慣。我不滿意的部分是潛在的性能影響。 現在,我知道沒有過早優化。我絕對編寫了代碼,沒有任何過早的優化,並且它的封裝足夠,以後優化它不應該是一個大問題。我已經對該方法進行了基準測試,以在相對適中的2GHz工作站上每秒執行大約250萬次轉換,而且我必須承認,與節省其他開發人員和提高可讀性的時間相比,這是驚人的性能。但是,考慮到下面的代碼示例,我覺得我濫用語言功能,並且可以做得更好。該方法與「HERE BE DRAGONS」xmldoc'ed大聲哭泣!我正在尋找更好的方法來避免雙重拳擊。爲簡潔起見,我省略的實際版本在許多情況下實際使用TryParse

public static TDestination As<TDestination> (this object source, TDestination defaultValue = default(TDestination)) 
{ 
    if (source is TDestination) 
     return (TDestination) source; 

    if (source == null || DbNull.Equals(source)) 
     return defaultValue; 

    if (TDestination is int) 
     return (TDestination) (object) Convert.ToInt32 (source.ToString()); 

    if (TDestination is long) 
     return (TDestination) (object) Convert.ToInt64 (source.ToString()); 

    if (TDestination is short) 
     return (TDestination) (object) Convert.ToInt16 (source.ToString()); 

    // and so on... 
} 
+0

你的'As '代碼示例不能編譯。 – phoog

回答

3

爲什麼不檢查,如果你的對象是IConvertible,並且,如果是,使用ToType:

var convertible = source as IConvertible; 
if (convertible != null) 
    return (TDestination)convertible.ToType(typeof(TDestination), Thread.CurrentThread.CurrentUICulture); 
+0

Ooooh我喜歡這個。 –

3

根據您的問題給出的例子As方法,你可能只是這樣做,而不是:

public static TDestination As<TDestination> 
    (this object source, TDestination defaultValue = default(TDestination)) 
{ 
    if ((source == null) || Convert.IsDBNull(source)) 
     return defaultValue; 

    return (TDestination)source; 
} 
+0

做一個像這樣的轉換行爲嗎?還是僅僅依靠隱式轉換? –

+0

@insta:不,它沒有。但是在調用'Convert.ToInt32'之前,你自己的'As'方法執行'if(source is int)'檢查,並且在調用'Convert.ToInt64'之前執行'if(source is long)'檢查等。如果你已經知道'source'是一個盒裝的'int',然後調用'ToString',然後調用'Convert.ToInt32',然後將一個boxing轉換爲'object',只是給你開始的東西 - 一個盒裝的'int' - 然後您將其轉換爲「TDestination」。 (類似於「長」,「短」等)爲什麼不避免無意義的往返並直接投射到「TDestination」呢? – LukeH

+0

哦,基督這是因爲我搞砸了這個問題。我的代碼實際上檢查typeof(TDestination)不是源代碼,這會改變遊戲的一點點。 –

2

每當我進入反射或檢查我的泛型類的T我要去使用字典Dictionary<Type, ???>。由於價值我總是把東西在每次應該做FuncAction。在你的情況,我會以這種方式,也許寫:

public static class MyConverter 
{ 
    private static Dictionary<Type, Func<object, object>> _MyConverter; 

    static MyConverter() 
    { 
     _MyConverter = new Dictionary<Type, Func<object, object>>(); 

     // Use the Add() method to include a lambda with the proper signature. 
     _MyConverter.Add(typeof(int), (source) => Convert.ToInt32 (source.ToString())); 

     // Use the index operator to include a lambda with the proper signature. 
     _MyConverter[typeof(double)] = (source) => Convert.ToDouble(source.ToString()); 

     // Use the Add() method to include a more complex lambda using curly braces. 
     _MyConverter.Add(typeof(decimal), (source) => 
     { 
      return Convert.ToDecimal(source.ToString()); 
     }); 

     // Use the index operator to include a function with the proper signature. 
     _MyConverter[typeof(float)] = MySpecialConverterFunctionForFloat; 
    } 

    // A function that does some more complex conversion which is simply unreadable as lambda. 
    private static object MySpecialConverterFunctionForFloat(object source) 
    { 
     var something = source as float?; 

     if (something != null 
      && something.HasValue) 
     { 
      return something.Value; 
     } 

     return 0; 
    } 

    public static TDestination As<TDestination>(this object source, TDestination defaultValue = default(TDestination)) 
    { 
     // Do some parameter checking (if needed). 
     if (source == null) 
      throw new ArgumentNullException("source"); 

     // The fast-path exit. 
     if (source.GetType().IsAssignableFrom(typeof(TDestination))) 
      return (TDestination)source; 

     Func<object, object> func; 

     // Check if a converter is available. 
     if (_MyConverter.TryGetValue(typeof(TDestination), out func)) 
     { 
      // Call it and return the result. 
      return (TDestination)func(source); 
     } 

     // Nothing found, so return the wished default. 
     return defaultValue; 
    } 
} 

這種方法唯一的缺點是的object使用導致(UN)拳擊這可能使得一些性能問題,如果函數一再呼籲常在很短的時間內。但一如既往之前的措施要求

另一方面,添加更多轉換器非常容易,並且由於字典的使用,您將始終爲O(1)。

+0

這看起來很棒。你有權訪問Visual Studio來測試它嗎?我發現構造函數存在問題,因爲它當時不知道「TDestination」。將字典初始化移動到'As'方法本身似乎無法達到緩存的目的:( –

0

如何定義DataRow字段屬性,你可以提供相同類型的自己的空值的字段,像這樣的擴展方法:

 public static T Field<T>(this DataRow row, string columnName, T nullValue) { return !row.IsNull(columnName) ? row.Field<T>(columnName) : nullValue; } 
0

我同意的是,製造場函數的變化是最好的方式,但如果你關心性能,那麼請不要使用ISNULL()或這些實際的Field函數執行了大量的冗餘檢查。以下方法是你真正需要的。

public static T Field<T>(this DataRow row, string columnName, T nullValue) 
{ 
    object value = row[columnName]; 
    return ((DBNull.Value == value) ? nullValue : (T)value); 
} 

這樣就不需要額外的拳擊發生,如果你小心你如何使用NullValue屬性參數,你可以放棄通常具有調用函數時顯式地指定噸。雙贏。

+0

雖然擴展方法並不特定於DataRow,但它只是最常用的用法。它作爲一種通用的鑄造和數據轉換方法 –

+0

在這種情況下,前面提到的靜態構造方法是最靈活的解決方案,但它的價格相當昂貴,如果你真的關心性能,你應該考慮製作專門的AS功能就像我爲最常見的情況提供的功能一樣,這樣他們可以儘可能優化。 – 0rigin

相關問題