可空类型作为泛型参数可能吗?

我想这样做:

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>是一个struct!因此,我尝试将类约束更改为struct约束(作为副作用,不能再返回null):

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

现在是作业:

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

给出以下错误:

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

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

285142 次浏览

将返回类型更改为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;
}

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

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

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

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;
}
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);
我认为你想处理引用类型和结构类型。 我使用它将XML Element字符串转换为更有类型的类型。 您可以使用反射删除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);

这个答案是有效的,但仅用于教育目的。詹姆斯·琼斯的解决方案可能是这里最好的,当然是我要去的。

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现在将是一个整型,或字符串,或任何从DB返回的类型。

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

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

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

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

public static T? GetValueOrNull<T>(this DbDataRecord reader, string columnName)
where T : struct
{
return reader[columnName] as 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时,您才能从数据库中得到一个合理的值。 用法:< / p >
...
decimal Quantity;
if (rdr.GetValueOrDefault<decimal>("YourColumnName", out Quantity))
{
// Do something with Quantity
}

这种方法非常类似于int.TryParse("123", out MyInt);

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

... = reader["myYear"] as int?;工作,是干净的。

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

多个通用约束不能以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;
}

较短的方法:

public static T ValueOrDefault<T>(this DataRow reader, string columnName) =>
reader.IsNull(columnName) ? default : (T) reader[columnName];

返回0用于int,返回null用于int?

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

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