You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C# DataRow自定义Get<T>方法疑问:类型转换与性能优化

C# DataRow 扩展方法相关疑问

我在使用C#从数据库获取数据时,了解到DataRow有Field<T>扩展方法:

public static T? Field<T>(this DataRow row, string columnName)

但我希望有一个更易用的方法,满足以下需求:

  • 当row["COLUMNNAME"]为null或DBNull时返回default(T)(我知道可以用int?,但不够便捷)
  • 访问Int16列时用Int32类型不会抛出异常(如dr.Field<int>("COMLUMINT16"))
  • 支持将int类型转换为string类型(如dr.Field<string>("COLUMNINT"))

于是我编写了一个替代版本:

public static T Get<T>(this DataRow row, string columnName)
{
    if (row == null || row.IsNull(columnName))
        return default(T);
    else
    {
        if (row[columnName].GetType() == typeof(T))
            return row.Field<T>(columnName);
        else
            return (T)row[columnName];
        //return (T)Convert.ChangeType(row[columnName], typeof(T));
    }
}

但我有两个疑问:

  1. 为什么直接使用return (T)row[columnName];会抛出“Unable to cast object of type 'System.Int16' to type 'System.Int32'”异常,而如下隐式转换却能正常工作:short x = 6; int y = x;
  2. 有没有比使用Convert类更简便的方法?听说Convert类的性能较差。

解答

针对第一个疑问

核心原因是装箱拆箱的类型匹配限制:

  • 当你写short x = 6; int y = x;时,这是编译阶段就能确定的值类型隐式转换,编译器会直接生成转换指令,把short的数值直接转成int类型,全程没有涉及装箱操作。
  • 但row[columnName]返回的是object类型,数据库返回的short已经被装箱成了object实例。CLR拆箱时有严格要求:拆箱的目标类型必须和装箱前的原始类型完全一致。你不能直接把装箱后的short对象拆箱成int,必须先拆成short,再转成int,这就是直接强制转换报错的原因。

针对第二个疑问

如果不想依赖Convert.ChangeType,可以根据场景选择以下两种更高效的方案:

方案1:针对常用类型做分支转换

对项目中高频使用的转换场景写分支判断,跳过Convert的通用逻辑开销,示例如下:

public static T Get<T>(this DataRow row, string columnName)
{
    if (row == null || row.IsNull(columnName))
        return default(T);

    var value = row[columnName];
    var targetType = typeof(T);

    // 类型直接匹配时直接返回
    if (value.GetType() == targetType)
        return (T)value;

    // 针对常用类型做定向转换
    if (targetType == typeof(int))
    {
        return (T)(object)Convert.ToInt32(value);
    }
    else if (targetType == typeof(long))
    {
        return (T)(object)Convert.ToInt64(value);
    }
    else if (targetType == typeof(string))
    {
        return (T)(object)(value?.ToString() ?? string.Empty);
    }
    // 可继续扩展double、bool等常用类型
    else
    {
        // 兜底用Convert处理小众转换场景
        return (T)Convert.ChangeType(value, targetType);
    }
}

这种方式对高频场景做了优化,性能比全用Convert要好很多,同时代码复杂度也不高。

方案2:用表达式树预编译转换逻辑

如果追求极致性能,可以用表达式树在首次调用时预编译转换委托,后续调用直接复用编译后的逻辑:

private static readonly Dictionary<Type, Dictionary<Type, Delegate>> _converterCache = new Dictionary<Type, Dictionary<Type, Delegate>>();

public static T Get<T>(this DataRow row, string columnName)
{
    if (row == null || row.IsNull(columnName))
        return default(T);

    var value = row[columnName];
    var sourceType = value.GetType();
    var targetType = typeof(T);

    if (sourceType == targetType)
        return (T)value;

    // 从缓存获取预编译的转换委托
    if (!_converterCache.TryGetValue(sourceType, out var targetMap) || !targetMap.TryGetValue(targetType, out var converter))
    {
        // 构建转换表达式并编译
        var paramExpr = Expression.Parameter(typeof(object), "obj");
        var unboxExpr = Expression.Convert(paramExpr, sourceType);
        var convertExpr = Expression.Convert(unboxExpr, targetType);
        var lambda = Expression.Lambda<Func<object, T>>(convertExpr, paramExpr);
        converter = lambda.Compile();

        // 缓存编译后的委托
        if (!_converterCache.ContainsKey(sourceType))
            _converterCache[sourceType] = new Dictionary<Type, Delegate>();
        _converterCache[sourceType][targetType] = converter;
    }

    return ((Func<object, T>)converter)(value);
}

表达式树只会在首次遇到特定类型转换时编译一次,后续调用的性能几乎和硬编码转换逻辑一致,适合转换场景多且性能要求高的项目。


内容的提问来源于stack exchange,提问作者b0bik

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.29 15:26:12