关于c#:Nullable类型作为通用参数可能吗?

Nullable type as a generic parameter possible?

我想这样做:

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

请注意,可以为空的类型是泛型参数。

由于GetValueOrNull函数可以返回空值,我的第一次尝试是:

1
2
3
4
5
6
7
8
9
10
11
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;
}

但我现在犯的错误是:

The type 'int?' must be a reference type in order to use it as parameter 'T' in the generic type or method

正确的!Nullable是一个struct!因此,我尝试将类约束更改为struct约束(作为副作用,不能再返回null):

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

现在的任务是:

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

给出以下错误:

The type 'int?' must be a non-nullable value type in order to use it as parameter 'T' in the generic type or method

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


将返回类型更改为nullable,并使用不可为nullable的参数调用方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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
2
3
4
5
6
7
8
9
public static T GetValueOrDefault<T>(this IDataRecord rdr, int index)
{
    object val = rdr[index];

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

    return default(T);
}

就这样使用:

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


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

另外,您可以通过将您的if语句更改为if (columnValue != DBNull.Value),避免使用is


免责声明:此答案有效,但仅用于教育目的。:)詹姆斯·琼斯的解决方案可能是这里最好的,当然也是我愿意采用的解决方案。

C 4.0的dynamic关键字使这更容易,如果不安全:

1
2
3
4
5
6
public static dynamic GetNullableValue(this IDataRecord record, string columnName)
{
  var val = reader[columnName];

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

现在,您不需要在rhs上使用显式类型提示:

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

事实上,你根本不需要它!

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

value现在将是int、string或从db返回的任何类型。

唯一的问题是,这并不能阻止您在lhs上使用不可为空的类型,在这种情况下,您将得到一个非常讨厌的运行时异常,比如:

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

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


只是做了一件不可思议的类似的事情。我的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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;
}

我认为您需要处理引用类型和结构类型。我使用它将XML元素字符串转换为更类型化的类型。可以通过反射移除nullAlternative。格式提供程序将处理与区域性相关的"."或","分隔符,例如小数或整数和双精度数。这可能会起作用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
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;
            }
        }
    }

您可以这样使用它:

1
2
3
4
5
6
7
8
9
10
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);


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

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


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

... = reader["myYear"] as int?;工作且清洁。

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


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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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时,数据库中的值才是合理的。用途:

1
2
3
4
5
6
...
decimal Quantity;
if (rdr.GetValueOrDefault<decimal>("YourColumnName", out Quantity))
{
    // Do something with Quantity
}

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


多个通用约束不能以"或"方式组合(限制性较小),只能以"和"方式组合(限制性更强)。这意味着一个方法不能同时处理这两个场景。泛型约束也不能用于为方法生成唯一的签名,因此必须使用两个单独的方法名。

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

在我的例子中,我特别希望返回空值,而不是任何可能值类型的默认值。GetValueOrDefault=错误。getValueOrNull=好。

我使用"null"和"nullable"来区分引用类型和值类型。下面是我编写的几个扩展方法的一个例子,它们对System.Linq.Enumerable类中的FirstOrDefault方法进行了补充。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
    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;
    }