94

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 ?

jeroenh
  • 26,362
  • 10
  • 73
  • 104

6 Answers6

149
void SetValue(PropertyInfo info, object instance, object value)
{
    info.SetValue(instance, Convert.ChangeType(value, info.PropertyType));
}
Thomas Levesque
  • 286,951
  • 70
  • 623
  • 758
  • 2
    Note that `Convert.ChangeType(value, property.PropertyType);` can still fail if `value` does not implement the `IConvertible` interface. For instance, if `info.PropertyType` is some `IEnumerable` – derekantrican Aug 25 '20 at 16:07
  • 3
    I know this question is old, but also notice that Convert.ChangeType can return a different value than plain casting. For example, `double d = 5.57293; int i = Convert(d, typeof(int))` will return 6. But doing `int i = (int)d` will return 5. – DDRider62 Mar 10 '22 at 23:02
50

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

Rafael
  • 2,642
  • 2
  • 24
  • 30
  • 2
    This is actually the answer I came looking for. Non-IConvertible dynamic casting. – jnm2 Jan 22 '15 at 21:27
  • 1
    Heh I would- if I was OP. – jnm2 Jan 23 '15 at 00:37
  • 1
    Was hoping this would save me when trying to cast `IEnumerable` (where those objects are strings) to `IEnumerable`. Unfortunately I'm getting errors like `Unable to cast object of type 'System.Collections.Generic.IEnumerable'1[System.Object]' to type 'System.Collections.Generic.IEnumerable'1[System.String]'.` – derekantrican Aug 25 '20 at 16:22
  • @derekantrican you need to iterate the list and cast each by your self. – shtse8 Feb 07 '21 at 15:38
45

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<>));
    }
}
Pavlos
  • 993
  • 7
  • 23
jeroenh
  • 26,362
  • 10
  • 73
  • 104
10

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);
Ignacio Calvo
  • 754
  • 9
  • 22
2

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);
VDP
  • 6,340
  • 4
  • 31
  • 53
Loukas
  • 616
  • 1
  • 6
  • 22
  • 2
    This problem is not specific to Guid but rather due to the fact that you get `DBNull.Value` instead of simply `null` when fetching null values from the database through ADO.Net. You will see the same with nullable int, for example. – jeroenh Feb 27 '13 at 10:27
0

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);
}
chakeda
  • 1,551
  • 1
  • 18
  • 40