'кастинг' з відображенням


81

Розглянемо такий зразок коду:

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 завжди представляє long, і це значення не завжди є десятковою. Однак я знаю, що значення можна відлити до правильного типу для цієї властивості.

Як я можу перетворити параметр 'value' на тип, представлений екземпляром PropertyInfo за допомогою відображення?

Відповіді:


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

1
Зверніть увагу, що Convert.ChangeType(value, property.PropertyType);все одно може вийти з ладу, якщо valueне реалізує IConvertibleінтерфейс. Наприклад, якщо info.PropertyTypeєIEnumerable
дерекантріканський

42

Відповідь Томаса працює лише для типів, що реалізують інтерфейс IConvertible:

Щоб перетворення було успішним, значення має реалізувати інтерфейс IConvertible, оскільки метод просто обертає виклик до відповідного методу IConvertible. Метод вимагає, щоб підтримувалось перетворення значення в conversionType.

Цей код компілює вираз 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 - заданий тип


2
Це насправді відповідь, яку я прийшов шукати. Неконвертоване динамічне лиття.
jnm2

1
Хе, я б - якби я був ОП.
jnm2

1
Сподівався, що це врятує мене при спробі приведення IEnumerable<object>(де ці об’єкти є рядками) до IEnumerable<string>. На жаль, я отримую такі помилки, якUnable to cast object of type 'System.Collections.Generic.IEnumerable'1[System.Object]' to type 'System.Collections.Generic.IEnumerable'1[System.String]'.
derekantrican

41

Відповідь Томаса правильна, але я подумав, що додам свою висновок, що 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<>));
  }

10

Сприяючи відповіді jeroenh, я хотів би додати, що Convert.ChangeType аварійно завершує роботу з нульовим значенням, тому рядок для отримання перетвореного значення повинен бути:

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

2

Коли тип є дозволом, що допускає нуль, тоді жодне із запропонованих вище рішень не працює. Недійсний викид із " System.DBNull'на" System.GuidвикидConvert.ChangeType

Щоб виправити цю зміну:

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

2
Ця проблема не характерна для Guid, а скоріше через те, що ви отримуєте DBNull.Valueзамість того, щоб просто отримувати nullнульові значення з бази даних через ADO.Net. Ви побачите те саме, наприклад, з nullable int.
jeroenh

0

Це дуже давнє запитання, але я думав, що буду брати участь у програмі 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);
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.