可空类型作为通用参数吗?


287

我想做这样的事情:

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

请注意,可为null的类型作为通用参数。

由于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>是一个struct!所以我尝试将类约束更改为struct约束(并且副作用不再返回null):

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

现在分配:

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

给出以下错误:

类型“ int?” 必须为非空值类型,才能在通用类型或方法中将其用作参数“ T”

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


3
IDataRecordDbDataRecord..
nawfal

Answers:


262

将返回类型更改为Nullable,然后使用non 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;
}

1
我建议您使用“ columnValue == DBNull.Value”代替'is'运算符,因为它稍快一些=)
driAn

40
个人喜好,但可以使用缩写T?而不是Nullable <T>
Dunc

11
这对于值类型来说很好,但是随后我认为它对引用类型(例如GetValueOrNull <string>)根本不起作用,因为C#似乎不像“ string?”那样喜欢Nullable <(ref type)>。如果您需要,下面的Robert C Barth和James Jones的解决方案对我来说似乎要好得多。
bacar 2011年

2
@bacar-对,因此是“ where T:struct”,如果您想要引用类型,则可以使用“ where T:class”创建类似的方法
Greg Dean

4
@Greg-可以,但是您需要第二种方法,并且您不能重载名称。正如我所说,如果您想同时处理val和ref类型,我认为此页面上提供了更清洁的解决方案。
bacar 2011年

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

6
可以简化为:return rdr.IsDBNull(index)?default(T):(T)rdr [index];
Foole,2011年

11
我认为这个问题明确想要null,而不是default(T)
mafu 2014年

5
@mafu default(T)对于引用类型将返回null,对于数字类型将返回0,从而使解决方案更加灵活。
詹姆斯·琼斯

2
我认为,更清楚地调用此方法GetValueOrDefault来澄清它返回default(T)而不是null。另外,如果T不能为空,则可以引发异常。
2014年

此方法有很多优点,并且迫使您考虑返回null以外的其他内容。
Shane

61

只需对原始代码做两件事–删除where约束,然后将最后一个return从更改return nullreturn default(T)。这样,您可以返回所需的任何类型。

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


4
此解决方案无效,因为NULL和0之间存在逻辑差异
Greg Dean

15
如果他传递的类型是int ?,则可以使用。就像他想要的那样,它将返回NULL。如果他将int作为类型传递,则由于int不能为NULL,它将返回0。除了我尝试过的事实之外,它还可以完美运行。
罗伯特·巴特

2
这是最正确,最灵活的答案。但是,return default就足够了(您不需要(T),编译器将从签名返回类型推断出它)。
McGuireV10

5

免责声明:此答案有效,但仅用于教育目的。:)詹姆斯·琼斯(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或一个字符串,或从数据库返回的任何类型。

唯一的问题是,这不会阻止您在LHS上使用不可为null的类型,在这种情况下,您将获得一个非常讨厌的运行时异常,例如:

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

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


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

3

我认为您想处理引用类型和结构类型。我使用它来将XML元素字符串转换为更多类型的类型。您可以使用反射删除nullAlternative。formatprovider用于处理文化相关的“。”。或','分隔符,例如小数或整数。这可能起作用:

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

2

这可能是一个死线程,但是我倾向于使用以下内容:

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

1
“类型'T'必须是不可为空的值类型,才能在通用类型或方法'Nullable <T>'中用作参数'T'”
Ian Warburton

1

我自己也遇到了同样的问题。

... = reader["myYear"] as int?; 可行,很干净。

它适用于任何类型,没有问题。如果结果为DBNull,则转换失败将返回null。


实际上,您可以执行int v=reader["myYear"]??-1;或其他一些默认操作来代替-1。但是,如果值是DBNull... ,这可能会带来问题
。– nurchi

1

我知道这很旧,但这是另一个解决方案:

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


如果您从事命名约定,那将是很好的。他们缺乏一致性。在一个地方,有一个变量没有大写,然后有一个没有大写。方法的参数相同。
MarinoŠimić17年

1
做完了!希望代码现在看起来更好。鲍勃(Bob)是你的姨妈:)都是skookum
nurchi

0

多个通用约束不能以“或”方式(限制较少)组合,而只能以“与”方式(限制较大)组合。意味着一种方法不能同时处理两种情况。通用约束也不能用于为方法创建唯一的签名,因此您必须使用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;
    }

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.