Question

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 
    //because 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?

Was it helpful?

Solution 3

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

OTHER TIPS

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.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top