是否有更通用的方法来做这种类型的行为
本文关键字:种类 类型 方法 是否 | 更新日期: 2023-09-27 18:12:49
我们从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'ed与"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...
}
为什么不检查你的对象是否是不可转换的,如果是,使用ToType:
var convertible = source as IConvertible;
if (convertible != null)
return (TDestination)convertible.ToType(typeof(TDestination), Thread.CurrentThread.CurrentUICulture);
根据问题中给出的As
方法示例,您可以这样做:
public static TDestination As<TDestination>
(this object source, TDestination defaultValue = default(TDestination))
{
if ((source == null) || Convert.IsDBNull(source))
return defaultValue;
return (TDestination)source;
}
每当我进入反射或检查我的泛型类的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)。
如何为datarow Field属性定义一个扩展方法,您可以在其中提供与Field相同类型的自己的空值,如下所示:
public static T Field<T>(this DataRow row, string columnName, T nullValue) { return !row.IsNull(columnName) ? row.Field<T>(columnName) : nullValue; }
我同意更改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。双赢。