Convert.ChangeType() fails on Nullable Types

Solution 1:

Untested, but maybe something like this will work:

string modelProperty = "Some Property Name";
string value = "Some Value";

var property = entity.GetType().GetProperty(modelProperty);
if (property != null)
{
    Type t = Nullable.GetUnderlyingType(property.PropertyType) ?? property.PropertyType;

    object safeValue = (value == null) ? null : Convert.ChangeType(value, t);

    property.SetValue(entity, safeValue, null);
}

Solution 2:

You have to get the underlying type in order to do that...

Try this, I've used it successfully with generics:

//Coalesce to get actual property type...
Type t = property.PropertyType();
t = Nullable.GetUnderlyingType(t) ?? t;

//Coalesce to set the safe value using default(t) or the safe type.
safeValue = value == null ? default(t) : Convert.ChangeType(value, t);

I use it in a number of places in my code, one example is a helper method I use for converting database values in a typesafe manner:

public static T GetValue<T>(this IDataReader dr, string fieldName)
{
    object value = dr[fieldName];

    Type t = typeof(T);
    t = Nullable.GetUnderlyingType(t) ?? t;

    return (value == null || DBNull.Value.Equals(value)) ? 
        default(T) : (T)Convert.ChangeType(value, t);
}

Called using:

string field1 = dr.GetValue<string>("field1");
int? field2 = dr.GetValue<int?>("field2");
DateTime field3 = dr.GetValue<DateTime>("field3");

I wrote a series of blog posts including this at http://www.endswithsaurus.com/2010_07_01_archive.html (Scroll down to the Addendum, @JohnMacintyre actually spotted the bug in my original code which led me down the same path you're on now). I have a couple of small modifications since that post that includes conversion of enum types also so if your property is an Enum you can still use the same method call. Just add a line in to check for enum types and you're off to the races using something like:

if (t.IsEnum)
    return (T)Enum.Parse(t, value);

Normally you'd have some error checking or use TryParse instead of Parse, but you get the picture.

Solution 3:

This is a little bit long-ish for an example, but this is a relatively robust approach, and separates the task of casting from unknown value to unknown type

I have a TryCast method that does something similar, and takes nullable types into account.

public static bool TryCast<T>(this object value, out T result)
{
    var type = typeof (T);

    // If the type is nullable and the result should be null, set a null value.
    if (type.IsNullable() && (value == null || value == DBNull.Value))
    {
        result = default(T);
        return true;
    }

    // Convert.ChangeType fails on Nullable<T> types.  We want to try to cast to the underlying type anyway.
    var underlyingType = Nullable.GetUnderlyingType(type) ?? type;

    try
    {
        // Just one edge case you might want to handle.
        if (underlyingType == typeof(Guid))
        {
            if (value is string)
            {
                value = new Guid(value as string);
            }
            if (value is byte[])
            {
                value = new Guid(value as byte[]);
            }

            result = (T)Convert.ChangeType(value, underlyingType);
            return true;
        }

        result = (T)Convert.ChangeType(value, underlyingType);
        return true;
    }
    catch (Exception ex)
    {
        result = default(T);
        return false;
    }
}

Of course TryCast is a Method with a Type Parameter, so to call it dynamically you have to construct the MethodInfo yourself:

var constructedMethod = typeof (ObjectExtensions)
    .GetMethod("TryCast")
    .MakeGenericMethod(property.PropertyType);

Then to set the actual property value:

public static void SetCastedValue<T>(this PropertyInfo property, T instance, object value)
{
    if (property.DeclaringType != typeof(T))
    {
        throw new ArgumentException("property's declaring type must be equal to typeof(T).");
    }

    var constructedMethod = typeof (ObjectExtensions)
        .GetMethod("TryCast")
        .MakeGenericMethod(property.PropertyType);

    object valueToSet = null;
    var parameters = new[] {value, null};
    var tryCastSucceeded = Convert.ToBoolean(constructedMethod.Invoke(null, parameters));
    if (tryCastSucceeded)
    {
        valueToSet = parameters[1];
    }

    if (!property.CanAssignValue(valueToSet))
    {
        return;
    }
    property.SetValue(instance, valueToSet, null);
}

And the extension methods to deal with property.CanAssignValue...

public static bool CanAssignValue(this PropertyInfo p, object value)
{
    return value == null ? p.IsNullable() : p.PropertyType.IsInstanceOfType(value);
}

public static bool IsNullable(this PropertyInfo p)
{
    return p.PropertyType.IsNullable();
}

public static bool IsNullable(this Type t)
{
    return !t.IsValueType || Nullable.GetUnderlyingType(t) != null;
}

Solution 4:

I had a similar need, and the answer from LukeH pointed me in the direction. I came up with this generic function to make it easy.

    public static Tout CopyValue<Tin, Tout>(Tin from, Tout toPrototype)
    {
        Type underlyingT = Nullable.GetUnderlyingType(typeof(Tout));
        if (underlyingT == null)
        { return (Tout)Convert.ChangeType(from, typeof(Tout)); }
        else
        { return (Tout)Convert.ChangeType(from, underlyingT); }
    }

Usage is like this:

        NotNullableDateProperty = CopyValue(NullableDateProperty, NotNullableDateProperty);

Note the second parameter is just used as a prototype to show the function how to cast the return value, so it doesn't actually have to be the destination property. Meaning you can do also do something like this:

        DateTime? source = new DateTime(2015, 1, 1);
        var dest = CopyValue(source, (string)null);

I did it this way instead of using an out because you can't use out with properties. As is, it can work with properties and variables. You could also create an overload to pass the type instead if you wanted.

Solution 5:

This works perfectly even for Nullable types:

TypeConverter conv = TypeDescriptor.GetConverter(type);
return conv.ConvertFrom(value);

For type safety you should also call conv.CanConvertFrom(type) method before calling ConvertFrom(). In case it returns false you can fallback to ChangeType or something else.