我想这样做:

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'

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


当前回答

多个通用约束不能以OR方式组合(限制较少),只能以AND方式组合(限制较多)。这意味着一个方法不能同时处理两种情况。泛型约束也不能用于为方法创建唯一的签名,因此必须使用两个单独的方法名。

但是,您可以使用泛型约束来确保正确使用方法。

在我的例子中,我特别希望返回null,而不是任何可能的值类型的默认值。GetValueOrDefault = bad。GetValueOrNull = good。

我使用“Null”和“Nullable”来区分引用类型和值类型。这里是我写的两个扩展方法的例子,它们补充了System.Linq.Enumerable类中的FirstOrDefault方法。

    public static TSource FirstOrNull<TSource>(this IEnumerable<TSource> source)
        where TSource: class
    {
        if (source == null) return null;
        var result = source.FirstOrDefault();   // Default for a class is null
        return result;
    }

    public static TSource? FirstOrNullable<TSource>(this IEnumerable<TSource?> source)
        where TSource : struct
    {
        if (source == null) return null;
        var result = source.FirstOrDefault();   // Default for a nullable is null
        return result;
    }

其他回答

免责声明:这个答案是有效的,但仅用于教育目的。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 T ValueOrDefault<T>(this DataRow reader, string columnName) => 
        reader.IsNull(columnName) ? default : (T) reader[columnName];

int返回0,int返回null ?

public static T GetValueOrDefault<T>(this IDataRecord rdr, int index)
{
    object val = rdr[index];

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

    return default(T);
}

就像这样使用它:

decimal? Quantity = rdr.GetValueOrDefault<decimal?>(1);
string Unit = rdr.GetValueOrDefault<string>(2);

只需要做一些不可思议的类似的事情。我的代码:

public T IsNull<T>(this object value, T nullAlterative)
{
    if(value != DBNull.Value)
    {
        Type type = typeof(T);
        if (type.IsGenericType && 
            type.GetGenericTypeDefinition() == typeof(Nullable<>).GetGenericTypeDefinition())
        {
            type = Nullable.GetUnderlyingType(type);
        }

        return (T)(type.IsEnum ? Enum.ToObject(type, Convert.ToInt32(value)) :
            Convert.ChangeType(value, type));
    }
    else 
        return nullAlternative;
}