我想这样做:

myYear = record.GetValueOrNull<int?>("myYear"),

注意将可空类型作为泛型参数。

因为GetValueOrNull函数可以返回null,我的第一次尝试是:

public static T GetValueOrNull<T>(this DbDataRecord reader, string columnName)
  where T : class
{
    object columnValue = reader[columnName];

    if (!(columnValue is DBNull))
    {
        return (T)columnValue;
    }
    return null;
}

但我现在得到的错误是:

类型'int?'必须是引用类型,以便在泛型类型或方法中使用它作为参数'T'

没错!Nullable<int>是一个结构体!所以我尝试将类约束改为结构约束(作为副作用不能再返回null):

public static T GetValueOrNull<T>(this DbDataRecord reader, string columnName)
  where T : struct

现在是作业:

myYear = record.GetValueOrNull<int?>("myYear");

给出以下错误:

类型'int?'必须是一个非空值类型,以便在泛型类型或方法中使用它作为参数'T'

是否可以将可空类型指定为泛型参数?


当前回答

我知道这很老了,但这里有另一个解决方案:

public static bool GetValueOrDefault<T>(this SqlDataReader Reader, string ColumnName, out T Result)
{
    try
    {
        object ColumnValue = Reader[ColumnName];

        Result = (ColumnValue!=null && ColumnValue != DBNull.Value) ? (T)ColumnValue : default(T);

        return ColumnValue!=null && ColumnValue != DBNull.Value;
    }
    catch
    {
        // Possibly an invalid cast?
        return false;
    }
}

现在,你不关心T是值类型还是引用类型。只有当函数返回true时,您才能从数据库中得到一个合理的值。 用法:

...
decimal Quantity;
if (rdr.GetValueOrDefault<decimal>("YourColumnName", out Quantity))
{
    // Do something with Quantity
}

这种方法与int非常相似。TryParse("123", out MyInt);

其他回答

免责声明:这个答案是有效的,但仅用于教育目的。James Jones的解决方案可能是最好的,当然也是我想要的。

c# 4.0的dynamic关键字让这变得更容易,虽然不太安全:

public static dynamic GetNullableValue(this IDataRecord record, string columnName)
{
  var val = reader[columnName];

  return (val == DBNull.Value ? null : val);
}

现在你不需要在RHS上显式的类型提示:

int? value = myDataReader.GetNullableValue("MyColumnName");

事实上,你在任何地方都不需要它!

var value = myDataReader.GetNullableValue("MyColumnName");

value现在将是一个int,或者一个字符串,或者任何从DB返回的类型。

唯一的问题是,这并不会阻止你在LHS上使用非空类型,在这种情况下,你会得到一个相当讨厌的运行时异常,比如:

Microsoft.CSharp.RuntimeBinder.RuntimeBinderException: Cannot convert null to 'int' because it is a non-nullable value type

与所有使用dynamic: caution编码器的代码一样。

只需对原始代码做两件事——删除where约束,并将最后一个返回从return null更改为return default(T)。这样你就可以返回你想要的任何类型。

顺便说一下,可以通过将if语句更改为if (columnValue != DBNull.Value)来避免使用is。

以防它帮助别人-我以前用过这个,似乎做什么我需要它…

public static bool HasValueAndIsNotDefault<T>(this T? v)
    where T : struct
{
    return v.HasValue && !v.Value.Equals(default(T));
}

下面是一个我用了很多年的扩展方法:

public static T GetValue<T>(this DbDataReader reader, string columnName)
{
    if (reader == null) throw new ArgumentNullException(nameof(reader));
    if (string.IsNullOrWhiteSpace(columnName))
        throw new ArgumentException("Value cannot be null or whitespace.", nameof(columnName));

    // do not swallow exceptions here - let them bubble up to the calling API to be handled and/or logged
    var index = reader.GetOrdinal(columnName);
    if (!reader.IsDBNull(index))
    {
        return (T)reader.GetValue(index);
    }
    return default;
}

将返回类型更改为Nullable<T>,并调用具有非Nullable参数的方法

static void Main(string[] args)
{
    int? i = GetValueOrNull<int>(null, string.Empty);
}


public static Nullable<T> GetValueOrNull<T>(DbDataRecord reader, string columnName) where T : struct
{
    object columnValue = reader[columnName];

    if (!(columnValue is DBNull))
        return (T)columnValue;

    return null;
}