我们从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万次转换,我必须承认这是非凡的性能,相比之下它节省了其他开发人员和我们获得的可读性提升。但是,考虑到下面的代码示例,我觉得我在滥用语言功能,可以做得更好。这个方法是xmldoc与“HERE BE DRAGONS”大声喊叫!我正在寻找一种更好的方法来避免双拳。为简洁起见,我省略了实际版本,实际上在许多情况下使用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 :(得分: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;
}
答案 1 :(得分:3)
为什么不检查您的对象是否是IConvertible,如果是,请使用ToType:
var convertible = source as IConvertible;
if (convertible != null)
return (TDestination)convertible.ToType(typeof(TDestination), Thread.CurrentThread.CurrentUICulture);
答案 2 :(得分:2)
每当我进入反思或检查我的泛型类的T
时,我将使用字典Dictionary<Type, ???>
。作为价值,我总是把它放在那里应该每次都做Func
或Action
。在你的情况下,我会以这种方式写它:
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)。
答案 3 :(得分:0)
如何为datarow Field属性定义扩展方法,您可以在其中提供与Field相同类型的空值,如下所示:
public static T Field<T>(this DataRow row, string columnName, T nullValue) { return !row.IsNull(columnName) ? row.Field<T>(columnName) : nullValue; }
答案 4 :(得分:0)
我同意对Field函数进行变换是最好的方法,但是如果你关注性能,那么不要使用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参数,你通常可以放弃在调用函数时必须明确指定T.双赢。