I have made a conversion method for handling the database values returned by procs. It looks like this:
public static T GetVerifiedValue<T>(this IDataRecord record, int index)
{
object value = record[index];
if (value is string)
value = string.IsNullOrEmpty(value as string) ? null : value;
Type nullableUnderlyingType = Nullable.GetUnderlyingType(typeof(T));
if (nullableUnderlyingType != null)
{
if (nullableUnderlyingType.IsEnum)
return value == null || value.Equals(DBNull.Value) ? default(T) : (T)Enum.ToObject(nullableUnderlyingType, value);
}
/*
//This is my try on solving the problem, but won't compile
//beca开发者_如何学编程use T has no struct constraint
if (value is ValueType)
{
ValueType structValue = (ValueType)value;
return value.Equals(DBNull.Value) ? default(T) : (T)structValue;
}
*/
return value == null || value.Equals(DBNull.Value) ? default(T) : (T)value;
}
The problem is that when the database returns an Interger, the value
variable will contain an int
, and when T
is a short
, I get an InvalidCastException
.
How can I improve this method to handle this situation? I want the method's user not to be concerned about this kind of problem, not having to double-cast. Is this possible?
You can only cast a boxed value type to the correct value type, then cast again to a different value type (if such a cast is supported).
There is, however, the Convert
class. If you had a boxed int
in value
, you could pass it to Convert.ToInt16(value)
and get a short
returned. As the number of types you'd probably use the Convert
class for (instead of casting) are few, a static generic helper method using a switch
would work well, if you went with the use of Convert
.
static bool MaybeConvert<TOutput>(object value, out TOutput result) {
output = default(TOutput);
switch(typeof(TOutput)) {
case typeof(short):
result = Convert.ToInt16(value);
return true;
...
default:
return false;
}
}
I use Convert
a lot with database results because sometimes even if you're working with 32-bit integer fields, if some math or aggregation was done, they're returned as 64-bit integers. Using Convert.ToInt32
is a lot easier than checking types and doing very specific casts myself.
Using the result as dynamic
and then casting should work in the presence of boxing.
public static T GetValue<T>(this IDataRecord record, int index)
{
dynamic result = record[index];
return (result == null || result == DBNull.Value) ? default(T) : (T)result;
}
By the way, there's an Eric Lippert blog post that deals with this one briefly.
I found a way using Convert.ChangeType(object, type)
(see the code comments for explanation):
public static T GetVerifiedValue<T>(this IDataRecord record, int index)
{
object value = record[index];
if (value == null || value.Equals(DBNull.Value))
return default(T);
//This handles nullable values, because sometimes there is a need for
//a casting on the underlying value before the final cast
Type nullableUnderlyingType = Nullable.GetUnderlyingType(typeof(T));
if (nullableUnderlyingType != null)
{
if (nullableUnderlyingType.IsEnum)
return (T)Enum.ToObject(nullableUnderlyingType, value);
else
return (T)Convert.ChangeType(value, nullableUnderlyingType);
}
//Enums must be handled before the ValueTypes, becouse
//enums are also ValueTypes and using Convert.ChangeType with it will fail
if (typeof(T).IsEnum)
return (T)Enum.ToObject(typeof(T), value);
//######################################################################
//Here is the trick: as Convert.ChangeType returns an object,
//it is compatible with the unconstrained T. Worked nicely.
if (value is ValueType)
{
ValueType structValue = (ValueType)value;
return (T)Convert.ChangeType(structValue, typeof(T));
}
//######################################################################
if (value is string)
value = string.IsNullOrEmpty(value as string) ? null : value;
return (T)value;
}
精彩评论