"кастинг" с отражением

Рассмотрим следующий пример кода:

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;
}

Теперь мне нужно сделать что-то подобное через отражение:

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

Обратите внимание, что я не могу предположить, что PropertyInfo всегда представляет длинный, и это значение всегда является десятичным. Тем не менее, я знаю, что значение может быть присвоено правильному типу для этого свойства.

Как преобразовать параметр "значение" в тип, представленный экземпляром PropertyInfo, через отражение?

Ответ 1

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

Ответ 2

Ответ Томаса прав, но я думал, что добавлю, что Convert.ChangeType не обрабатывает преобразование в типы с нулевым значением. Для обработки типов с нулевым значением я использовал следующий код:

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);
}

В этом коде используется следующий метод расширения:

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

Ответ 3

Томас отвечает только на типы, реализующие интерфейс IConvertible:

Для успешной конверсии значение должно реализовать интерфейс IConvertible, потому что метод просто переносит вызов на соответствующий метод IConvertible. Этот метод требует поддержки преобразования значения в тип преобразования.

Этот код компилирует выражение linq, которое выполняет распаковку (при необходимости) и преобразование:

    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;
    }

Полученное лямбда-выражение равно (TOUT) (TIn) данных, где TIn - тип исходных данных, а TOUT - заданный тип

Ответ 4

В ответ на jeroenh answer я добавлю, что Convert.ChangeType вылетает с нулевым значением, поэтому строка для получения преобразованного значения должна быть:

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

Ответ 5

Когда Тип является Nullable Guid, ни одно из вышеперечисленных предлагаемых решений не работает. Недопустимый сброс из 'System.DBNull' в 'System.Guid' исключение выбрано в Convert.ChangeType

Чтобы исправить это изменение:

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

Ответ 6

Это очень старый вопрос, но я решил присоединиться к ASP.NET Core Googlers.

В ASP.NET Core .IsNullableType() защищен (среди других изменений), поэтому код немного отличается. Вот ответ @jeroenh, модифицированный для работы в 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);
}