2011-10-27 27 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

相关问题