Search code examples
c#genericscastingunboxing

Unboxing -1 and casting to Nullable<int> using generics yields InvalidCastException


In this SO post I found a generic extension method which returns a default value if a value read by an SqlDataReader is null or DBNull.Value, and the correctly converted value otherwise. I implemented it like this:

public static T GetValueOrDefault<T>(this SqlDataReader reader, string columnName, T defaultValue = default(T))
{
  object val = reader[columnName];
  if (val == null || val == DBNull.Value)
  {
    return defaultValue;
  }

  return (T)val;
}

The examples mentioned below the method in the post linked above don't use the correct syntax, but nevertheless suggest that this should also work with nullable types such as int?.

However, I read a nullable int column from the database like this:

MyProperty = reader.GetValueOrDefault<int?>("SomeColumnName")

where debug mode shows me that val is -1 and T is int?, but I get an InvalidCastException. From this post I figured it's because unboxing and casting can't be performed in a single operation (val is of type object holding a short value), but what can I do to make it work in my case? Since it's a generic method, I wouldn't know how to manually unbox it before doing the conversion.


Solution

  • For a really good answer, you need to post a good, minimal, complete code example.

    If a cast to int fails, then the boxed value isn't an int. You should be able to see in the debugger what it actually is.

    That said, the Convert class is a lot more sophisticated about conversions; the cast will require an exact match, i.e. the boxed value must be an int (the literal -1 would be an int, but in your case the value's coming from who-knows-where and easily could be a short or long or whatever).

    So instead of the cast, a call e.g. to Convert.ToInt32() would work better.

    In your case, with the generic method (i.e. the type is unknown at compile-time), the Convert.ChangeType() method is probably more appropriate:

    public static T GetValueOrDefault<T>(this SqlDataReader reader, string columnName, T defaultValue = default(T))
    {
      object val = reader[columnName];
      if (val == null || val == DBNull.Value)
      {
        return defaultValue;
      }
    
      return (T)Convert.ChangeType(val, typeof(T));
    }