我想做这样的事情:
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”
是否尽可能将可空类型指定为泛型参数?
DbDataRecord
处签名 IDataRecord
..
将返回类型更改为 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;
}
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);
GetValueOrDefault
以澄清它返回 default(T)
而不是 null
更清楚。或者,如果 T
不可为空,您可以让它抛出异常。
只需对您的原始代码做两件事 - 删除 where
约束,并将最后一个 return
从 return null
更改为 return default(T)
。这样你就可以返回你想要的任何类型。
顺便说一句,您可以通过将 if
语句更改为 if (columnValue != DBNull.Value)
来避免使用 is
。
return default
就足够了(您不需要 (T)
,编译器会从签名返回类型推断它)。
免责声明:此答案有效,但仅用于教育目的。 :) James Jones' solution 可能是这里最好的,当然也是我会选择的。
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
的代码一样:caveat coder。
我认为您想处理引用类型和结构类型。我使用它来将 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);
多个通用约束不能以 OR 方式(限制较少)组合,只能以 AND 方式(限制较多)组合。这意味着一种方法不能同时处理这两种情况。通用约束也不能用于为方法制作唯一签名,因此您必须使用 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;
}
只需要做一些与此类似的令人难以置信的事情。我的代码:
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 bool HasValueAndIsNotDefault<T>(this T? v)
where T : struct
{
return v.HasValue && !v.Value.Equals(default(T));
}
这可能是一个死线程,但我倾向于使用以下内容:
public static T? GetValueOrNull<T>(this DbDataRecord reader, string columnName)
where T : struct
{
return reader[columnName] as T?;
}
较短的方式:
public static T ValueOrDefault<T>(this DataRow reader, string columnName) =>
reader.IsNull(columnName) ? default : (T) reader[columnName];
为 int
返回 0
,为 int?
返回 null
我自己也遇到了同样的问题。
... = reader["myYear"] as int?;
有效且干净。
它适用于任何类型而没有问题。如果结果为 DBNull,则在转换失败时返回 null。
int v=reader["myYear"]??-1;
或其他一些默认值来代替 -1
。但是,如果值为 DBNull
,这可能会带来问题...
我知道这很旧,但这是另一种解决方案:
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);
非常相似
这是我多年来使用的一种扩展方法:
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;
}
不定期副业成功案例分享