88
votes

Consider the following sample code:

class SampleClass
{
    public long SomeProperty { get; set; }
}

public void SetValue(SampleClass instance, decimal value)
{
    // value is of type decimal, but is in reality a natural number => cast
    instance.SomeProperty = (long)value;
}

Now I need to do something similar through reflection:

void SetValue(PropertyInfo info, object instance, object value)
{
    // throws System.ArgumentException: Decimal can not be converted to Int64
    info.SetValue(instance, value)  
}

Note that I cannot assume that the PropertyInfo always represents a long, neither that value is always a decimal. However, I know that value can be casted to the correct type for that property.

How can I convert the 'value' parameter to the type represented by PropertyInfo instance through reflection ?

6

6 Answers

143
votes
void SetValue(PropertyInfo info, object instance, object value)
{
    info.SetValue(instance, Convert.ChangeType(value, info.PropertyType));
}
45
votes

Thomas answer only works for types that implement IConvertible interface:

For the conversion to succeed, value must implement the IConvertible interface, because the method simply wraps a call to an appropriate IConvertible method. The method requires that conversion of value to conversionType be supported.

This code compile a linq expression that does the unboxing (if needed) and the conversion:

    public static object Cast(this Type Type, object data)
    {
        var DataParam = Expression.Parameter(typeof(object), "data");
        var Body = Expression.Block(Expression.Convert(Expression.Convert(DataParam, data.GetType()), Type));

        var Run = Expression.Lambda(Body, DataParam).Compile();
        var ret = Run.DynamicInvoke(data);
        return ret;
    }

The resulting lambda expression equals to (TOut)(TIn)Data where TIn is the type of the original data and TOut is the given type

44
votes

The answer by Thomas is right, but I thought I would add my finding that Convert.ChangeType does not handle conversion to nullable types. To handle nullable types, I used the following code:

void SetValue(PropertyInfo info, object instance, object value)
{
    var targetType = info.PropertyType.IsNullableType() 
         ? Nullable.GetUnderlyingType(info.PropertyType) 
         : info.PropertyType; 
    var convertedValue = Convert.ChangeType(value, targetType);

    info.SetValue(instance, convertedValue, null);
}

This code makes use of the following extension method:

public static class TypeExtensions
{
    public static bool IsNullableType(this Type type)
    {
        return type.IsGenericType
               && type.GetGenericTypeDefinition().Equals(typeof(Nullable<>));
    }
}
10
votes

Contributing to jeroenh's answer, I would add that Convert.ChangeType crashes with a null value, so the line for getting the converted value should be:

var convertedValue = value == null ? null : Convert.ChangeType(value, targetType);
2
votes

When the Type is a Nullable Guid then none of the above proposed solutions work. Invalid cast from 'System.DBNull' to 'System.Guid' exception is thrown at Convert.ChangeType

To fix that change to:

var convertedValue = value == System.DBNull.Value ? null : Convert.ChangeType(value, targetType);
0
votes

This is a very old question but I thought I'd chime in for ASP.NET Core Googlers.

In ASP.NET Core, .IsNullableType() is protected (amongst other changes) so the code is a tad different. Here's @jeroenh's answer modified to work in ASP.NET Core:

void SetValue(PropertyInfo info, object instance, object value)
{
    Type proptype = info.PropertyType;
    if (proptype.IsGenericType && proptype.GetGenericTypeDefinition().Equals(typeof(Nullable<>)))
    {
        proptype = new NullableConverter(info.PropertyType).UnderlyingType;
    }

    var convertedValue = Convert.ChangeType(value, proptype);
    info.SetValue(instance, convertedValue);
}