101
Type t = typeof(int?); //will get this dynamically
object val = 5; //will get this dynamically
object nVal = Convert.ChangeType(val, t);//getting exception here

I am getting InvalidCastException in above code. For above I could simply write int? nVal = val, but above code is executing dynamically.

I am getting a value(of non nullable type like int, float, etc) wrapped up in an object (here val), and I have to save it to another object by casting it to another type(which can or cannot be nullable version of it). When

Invalid cast from 'System.Int32' to 'System.Nullable`1[[System.Int32, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]'.

An int, should be convertible/type-castable to nullable int, what is the issue here ?

Soner Gönül
  • 97,193
  • 102
  • 206
  • 364
Brij
  • 11,731
  • 22
  • 78
  • 116
  • 1
    I guess maybe coz `Nullable` doesn't implement `IConvertible` – V4Vendetta Aug 02 '13 at 11:16
  • 2
    This is rather fundamental. Nullable is special, when you put it in an object then it either becomes null or becomes a boxed value of the value type. So asking for an int? stored in an object just doesn't make sense. Just ask for int. – Hans Passant Aug 02 '13 at 11:37

3 Answers3

190

You have to use Nullable.GetUnderlyingType to get underlying type of Nullable.

This is the method I use to overcome limitation of ChangeType for Nullable

public static T ChangeType<T>(object value) 
{
   var t = typeof(T);

   if (t.IsGenericType && t.GetGenericTypeDefinition().Equals(typeof(Nullable<>))) 
   {
       if (value == null) 
       { 
           return default(T); 
       }

       t = Nullable.GetUnderlyingType(t);
   }

   return (T)Convert.ChangeType(value, t);
}

non generic method:

public static object ChangeType(object value, Type conversion) 
{
   var t = conversion;

   if (t.IsGenericType && t.GetGenericTypeDefinition().Equals(typeof(Nullable<>))) 
   {
       if (value == null) 
       { 
           return null; 
       }

       t = Nullable.GetUnderlyingType(t);
   }

   return Convert.ChangeType(value, t);
}
David Gardiner
  • 16,892
  • 20
  • 80
  • 117
gzaxx
  • 17,312
  • 2
  • 36
  • 54
  • to user your method, I would need to do something like: `object nVal = ChangeType(val)`, here I need to tell the method about the generic argument(T), but I have `t` (or typeof(dataType)) at my disposal. How would I call your ChangeType method in my scenario ? – Brij Aug 02 '13 at 11:35
  • 1
    Added non generic version. See if it helps. – gzaxx Aug 02 '13 at 11:55
  • Getting compile error at `default(conversion)`, seems like similar issue. – Brij Aug 02 '13 at 11:58
  • Careful @gzaxx as `return null` is not the same as `default(T)`. If you're dealing with structs they are completely different things. – Alex Mar 02 '16 at 23:32
  • Non generic version will box struct and primitive types (as it takes and returns object) so returning null is valid. Anyone calling the functions will have to handle this by themselves. – gzaxx Mar 04 '16 at 07:06
  • I would suggest two more changes - do the null-check first (perfomance) and use "is" instead of == to avoid operator overloading surprises. so start before getting the type t to see `if (value is null) return default(T);` - your thoughts? – iJungleBoy Sep 25 '21 at 11:46
  • Sorry, but I think this answer has an error. I do not understand so many votes here. The question is how to cast int no nullable. Non-generic version is wrong because Nullable.GetUnderlying(int?) returns int. Thus you are doing here Convert.ChangeType(int, int). One thing helping here ia a direct cast to (T). But Convert.ChangeType() is wrong and not useful here. – Anton Semenov Oct 25 '21 at 11:54
13

For above I could simply write int? nVal = val

Actually, you can't do that either. There is no implicit conversion from object to Nullable<int>. But there is an implicit conversion from int to Nullable<int> so you can write this:

int? unVal = (int)val;

You can use Nullable.GetUnderlyingType method.

Returns the underlying type argument of the specified nullable type.

A generic type definition is a type declaration, such as Nullable, that contains a type parameter list, and the type parameter list declares one or more type parameters. A closed generic type is a type declaration where a particular type is specified for a type parameter.

Type t = typeof(int?); //will get this dynamically
Type u = Nullable.GetUnderlyingType(t);
object val = 5; //will get this dynamically
object nVal = Convert.ChangeType(val, u);// nVal will be 5

Here's a DEMO.

Nae
  • 14,209
  • 7
  • 52
  • 79
Soner Gönül
  • 97,193
  • 102
  • 206
  • 364
2

I think I should explain why the function does not work:

1- The line that throw the exception is as follows:

throw new InvalidCastException(Environment.GetResourceString("InvalidCast_FromTo", new object[]
  {
    value.GetType().FullName, 
    targetType.FullName
    }));

in fact the function search in the array Convert.ConvertTypes after that it see if the targer is an Enum and when nothing is found it throw the exception above.

2- the Convert.ConvertTypes is initialized as:

Convert.ConvertTypes = new RuntimeType[]
   {
      (RuntimeType)typeof(Empty), 
      (RuntimeType)typeof(object), 
      (RuntimeType)typeof(DBNull), 
      (RuntimeType)typeof(bool), 
      (RuntimeType)typeof(char), 
      (RuntimeType)typeof(sbyte), 
      (RuntimeType)typeof(byte), 
      (RuntimeType)typeof(short), 
      (RuntimeType)typeof(ushort), 
      (RuntimeType)typeof(int), 
      (RuntimeType)typeof(uint), 
      (RuntimeType)typeof(long), 
      (RuntimeType)typeof(ulong), 
      (RuntimeType)typeof(float), 
      (RuntimeType)typeof(double), 
      (RuntimeType)typeof(decimal), 
      (RuntimeType)typeof(DateTime), 
      (RuntimeType)typeof(object), 
      (RuntimeType)typeof(string)
   };

So since the int? is not in the ConvertTypes array and not an Enum the exception is thrown.

So to resume, for the function Convert.ChnageType to work you have:

  1. The object to be converted is IConvertible

  2. The target type is within the ConvertTypes and not Empty nor DBNull (There is an explict test on those two with throw exception)

This behaviour is because int (and all other default types) uses Convert.DefaultToType as IConvertibale.ToType implementation. and here is the code of theDefaultToTypeextracted using ILSpy

internal static object DefaultToType(IConvertible value, Type targetType, IFormatProvider provider)
{
    if (targetType == null)
    {
        throw new ArgumentNullException("targetType");
    }
    RuntimeType left = targetType as RuntimeType;
    if (left != null)
    {
        if (value.GetType() == targetType)
        {
            return value;
        }
        if (left == Convert.ConvertTypes[3])
        {
            return value.ToBoolean(provider);
        }
        if (left == Convert.ConvertTypes[4])
        {
            return value.ToChar(provider);
        }
        if (left == Convert.ConvertTypes[5])
        {
            return value.ToSByte(provider);
        }
        if (left == Convert.ConvertTypes[6])
        {
            return value.ToByte(provider);
        }
        if (left == Convert.ConvertTypes[7])
        {
            return value.ToInt16(provider);
        }
        if (left == Convert.ConvertTypes[8])
        {
            return value.ToUInt16(provider);
        }
        if (left == Convert.ConvertTypes[9])
        {
            return value.ToInt32(provider);
        }
        if (left == Convert.ConvertTypes[10])
        {
            return value.ToUInt32(provider);
        }
        if (left == Convert.ConvertTypes[11])
        {
            return value.ToInt64(provider);
        }
        if (left == Convert.ConvertTypes[12])
        {
            return value.ToUInt64(provider);
        }
        if (left == Convert.ConvertTypes[13])
        {
            return value.ToSingle(provider);
        }
        if (left == Convert.ConvertTypes[14])
        {
            return value.ToDouble(provider);
        }
        if (left == Convert.ConvertTypes[15])
        {
            return value.ToDecimal(provider);
        }
        if (left == Convert.ConvertTypes[16])
        {
            return value.ToDateTime(provider);
        }
        if (left == Convert.ConvertTypes[18])
        {
            return value.ToString(provider);
        }
        if (left == Convert.ConvertTypes[1])
        {
            return value;
        }
        if (left == Convert.EnumType)
        {
            return (Enum)value;
        }
        if (left == Convert.ConvertTypes[2])
        {
            throw new InvalidCastException(Environment.GetResourceString("InvalidCast_DBNull"));
        }
        if (left == Convert.ConvertTypes[0])
        {
            throw new InvalidCastException(Environment.GetResourceString("InvalidCast_Empty"));
        }
    }
    throw new InvalidCastException(Environment.GetResourceString("InvalidCast_FromTo", new object[]
    {
        value.GetType().FullName, 
        targetType.FullName
    }));
}

in other hand the cast is implemented by Nullable class itself and the definition is:

public static implicit operator T?(T value)
{
    return new T?(value);
}
public static explicit operator T(T? value)
{
    return value.Value;
}
Swift
  • 1,861
  • 14
  • 17