是否有更通用的方法来做这种类型的行为

本文关键字:种类 类型 方法 是否 | 更新日期: 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, ???>。作为值,我总是把每次应该做的事情作为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)。

如何为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。双赢。