可空类型作为泛型参数是否可行?

363
我想要做类似于这样的事情:
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 是一个struct!因此,我尝试将类约束更改为struct约束(并且副作用是不能再返回null):
public static T GetValueOrNull<T>(this DbDataRecord reader, string columnName)
  where T : struct

现在开始任务:
myYear = record.GetValueOrNull<int?>("myYear");

给出以下错误:

在泛型类型或方法中,参数'T'必须是非空值类型,而'type int?'却不是

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


3
请将您的签名从DbDataRecord更改为IDataRecord - nawfal
13个回答

305

将返回类型更改为Nullable<T>,并使用非可空参数调用该方法

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;
}

2
我建议您使用“columnValue == DBNull.Value”而不是“is”运算符,因为它稍微快一点 =) - driAn
54
个人偏好,但你可以使用T?的简写形式代替Nullable<T>。 - Dunc
11
对于值类型这样做是可以的,但我认为对于引用类型(例如 GetValueOrNull<string>)则完全行不通,因为 C# 似乎不支持像 "string?" 这样的可空引用类型。如果您有这方面的需求,Robert C Barth 和 James Jones 的解决方案可能更好。 - bacar
2
@bacar - 是的,因此需要使用“where T : struct”,如果您想要引用类型,可以创建一个类似的方法,并使用“where T: class”。 - Greg Dean
4
@Greg - 当然,但是您需要第二种方法,并且不能重载名称。就像我所说的,如果您想处理val和ref类型,我认为这个页面提供了更清晰的解决方案。 - bacar
显示剩余4条评论

126
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);

7
可以简化为:return rdr.IsDBNull(index) ? default(T) : (T)rdr[index]; - Foole
19
我认为这个问题明确地想要“null”,而不是“default(T)” 。 - mafu
8
@mafu 的代码中,对于引用类型,default(T)会返回null;对于数值类型,会返回0。这样做可以让解决方案更加灵活。 - James Jones
2
我认为更清晰的方式是将其命名为 GetValueOrDefault,以澄清它返回的是 default(T) 而不是 null。另外,如果 T 不可为空,则可以让它抛出异常。 - Sam
这种方法有很多优点,并且迫使你考虑返回除了 null 以外的其他内容。 - Shanerk
@mafu 对于值类型,您可以明确告诉它您正在寻找空值:GetValueOrDefault<decimal?>。 - avs099

68

对原始代码仅需进行两个操作- 移除 where 约束,并将最后一个 returnreturn null 更改为 return default(T)。 这样,您可以返回任何类型。

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


11
此解决方案不可行,因为NULL和0之间存在逻辑差异。 - Greg Dean
19
如果他传递的类型是int?,它会起作用。它会返回NULL,就像他想要的那样。如果他传递int作为类型,它会返回0,因为int不能为NULL。此外,事实是我已经尝试过它,而且它完美地工作了。 - Robert C. Barth
2
这是最正确和灵活的答案。然而,return default 就足够了(你不需要 (T),编译器会从签名返回类型中推断出来)。 - McGuireV10

7

免责声明:此答案可行,但仅用于教育目的。 :) 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、字符串或从数据库返回的任何类型。

唯一的问题是,这并不能防止您在左侧使用非空类型,这种情况下将出现一个相当令人不快的运行时异常,例如:

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

与所有使用 dynamic 的代码一样:请注意编码。

3
在这种情况下,我会避免使用动态类型,因为它们并不必要。动态类型会带来显著的性能开销。 - Dave Black

7
多个通用约束不能以或的方式(限制较少)组合,只能以和的方式(限制更多)组合。这意味着一个方法不能处理两种情况。通用约束也不能用于为方法创建唯一签名,因此您需要使用2个单独的方法名称。
但是,您可以使用通用约束确保方法正确使用。
在我的情况下,我特别希望返回null,并且永远不返回任何可能值类型的默认值。GetValueOrDefault = 糟糕。GetValueOrNull = 好。
我使用单词“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;
    }

6

我认为你想处理引用类型和结构类型。 我使用它将XML元素字符串转换为更具类型的类型。 您可以使用反射删除nullAlternative。 格式提供程序可用于处理特定于文化的'.'或','分隔符,例如十进制数、整数和双精度浮点数。 以下可能有效:

public T GetValueOrNull<T>(string strElementNameToSearchFor, IFormatProvider provider = null ) 
    {
        IFormatProvider theProvider = provider == null ? Provider : provider;
        XElement elm = GetUniqueXElement(strElementNameToSearchFor);

        if (elm == null)
        {
            object o =  Activator.CreateInstance(typeof(T));
            return (T)o; 
        }
        else
        {
            try
            {
                Type type = typeof(T);
                if (type.IsGenericType &&
                type.GetGenericTypeDefinition() == typeof(Nullable<>).GetGenericTypeDefinition())
                {
                    type = Nullable.GetUnderlyingType(type);
                }
                return (T)Convert.ChangeType(elm.Value, type, theProvider); 
            }
            catch (Exception)
            {
                object o = Activator.CreateInstance(typeof(T));
                return (T)o; 
            }
        }
    }

您可以这样使用它:
iRes = helper.GetValueOrNull<int?>("top_overrun_length");
Assert.AreEqual(100, iRes);



decimal? dRes = helper.GetValueOrNull<decimal?>("top_overrun_bend_degrees");
Assert.AreEqual(new Decimal(10.1), dRes);

String strRes = helper.GetValueOrNull<String>("top_overrun_bend_degrees");
Assert.AreEqual("10.1", strRes);

4

我最近也做了类似的事情。我的代码:

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;
}

4
更短的方法:
public static T ValueOrDefault<T>(this DataRow reader, string columnName) => 
        reader.IsNull(columnName) ? default : (T) reader[columnName];

对于int类型,返回0;对于int?类型,返回null


4

如果有人需要的话,我的经验是我之前使用过这个并且似乎可以满足我的需求...

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

3

这可能是一个没有回应的帖子,但我倾向于使用以下方法:

public static T? GetValueOrNull<T>(this DbDataRecord reader, string columnName)
where T : struct 
{
    return reader[columnName] as T?;
}

2
“类型'T'必须是非可空值类型,才能在泛型类型或方法“Nullable<T>”中用作参数'T'。” - Ian Warburton

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接