„rzucanie” z refleksją

81

Rozważmy następujący przykładowy kod:

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

Teraz muszę zrobić coś podobnego poprzez refleksję:

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

Zauważ, że nie mogę założyć, że PropertyInfo zawsze reprezentuje długość, ani ta wartość nie zawsze jest dziesiętna. Wiem jednak, że wartość można rzutować na odpowiedni typ dla tej właściwości.

Jak mogę przekonwertować parametr „value” na typ reprezentowany przez instancję PropertyInfo poprzez odbicie?

jeroenh
źródło

Odpowiedzi:

134
void SetValue(PropertyInfo info, object instance, object value)
{
    info.SetValue(instance, Convert.ChangeType(value, info.PropertyType));
}
Thomas Levesque
źródło
1
Zauważ, że Convert.ChangeType(value, property.PropertyType);nadal może się nie powieść, jeśli valuenie zaimplementuje IConvertibleinterfejsu. Na przykład, jeśli info.PropertyTypejest jakiśIEnumerable
derekantrican
42

Odpowiedź Thomasa działa tylko dla typów, które implementują interfejs IConvertible:

Aby konwersja zakończyła się pomyślnie, value musi zaimplementować interfejs IConvertible, ponieważ metoda po prostu opakowuje wywołanie do odpowiedniej metody IConvertible. Metoda wymaga obsługi konwersji wartości na typ konwersji.

Ten kod kompiluje wyrażenie linq, które wykonuje rozpakowywanie (w razie potrzeby) i konwersję:

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

Wynikowe wyrażenie lambda jest równe (TOut) (TIn) Data, gdzie TIn to typ oryginalnych danych, a TOut to podany typ

Rafael
źródło
2
Właściwie to jest odpowiedź, której szukałem. Rzutowanie dynamiczne non-IConvertible.
jnm2
1
Heh ja bym - gdybym był OP.
jnm2
1
Miałem nadzieję, że to mnie uratuje, gdy próbuję rzucać IEnumerable<object>(tam, gdzie te obiekty są łańcuchami) IEnumerable<string>. Niestety Unable to cast object of type 'System.Collections.Generic.IEnumerable'1[System.Object]' to type 'System.Collections.Generic.IEnumerable'1[System.String]'.
pojawiają
41

Odpowiedź Thomasa jest poprawna, ale pomyślałem, że dodam swoje odkrycie, że Convert.ChangeType nie obsługuje konwersji na typy dopuszczające wartość null. Aby obsłużyć typy dopuszczające wartość null, użyłem następującego kodu:

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

Ten kod wykorzystuje następującą metodę rozszerzenia:

public static class TypeExtensions
{
  public static bool IsNullableType(this Type type)
  {
    return type.IsGenericType 
    && type.GetGenericTypeDefinition().Equals(typeof(Nullable<>));
  }
jeroenh
źródło
10

Przyczyniając się do odpowiedzi Jeroenh, dodałbym, że Convert.ChangeType ulega awarii z wartością null, więc wiersz do uzyskania przekonwertowanej wartości powinien wyglądać następująco:

var convertedValue = value == null ? null : Convert.ChangeType(value, targetType);
Ignacio Calvo
źródło
2

Gdy typ jest wartością Nullable Guid, żadne z proponowanych powyżej rozwiązań nie działa. W miejscu zgłaszany jest nieprawidłowy rzut z wyjątku „ System.DBNull„ do ”System.GuidConvert.ChangeType

Aby naprawić tę zmianę w:

var convertedValue = value == System.DBNull.Value ? null : Convert.ChangeType(value, targetType);
Loukas
źródło
2
Ten problem nie jest specyficzny dla Guid, ale raczej wynika z faktu, że DBNull.Valuezamiast po prostu pobierać nullwartości null z bazy danych przez ADO.Net. To samo zobaczysz na przykład z wartością nullable int.
jeroenh
0

To jest bardzo stare pytanie, ale pomyślałem, że wbijam się w pracowników Google w ASP.NET Core.

W ASP.NET Core .IsNullableType()jest chroniony (między innymi), więc kod jest nieco inny. Oto odpowiedź @ jeroenh's zmodyfikowana do pracy w 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
źródło