有没有更通用的方法来做这种行为?

时间:2011-10-27 22:00:27

标签: c# generics

我们从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...
}

5 个答案:

答案 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, ???>。作为价值,我总是把它放在那里应该每次都做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)。

答案 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.双赢。