"Casting" mit Reflexion

81

Betrachten Sie den folgenden Beispielcode:

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

Jetzt muss ich etwas Ähnliches durch Nachdenken tun:

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

Beachten Sie, dass ich nicht davon ausgehen kann, dass PropertyInfo immer eine lange Zahl darstellt, und dass dieser Wert auch nicht immer eine Dezimalzahl ist. Ich weiß jedoch, dass der Wert für diese Eigenschaft in den richtigen Typ umgewandelt werden kann.

Wie kann ich den Parameter 'value' durch Reflektion in den von der PropertyInfo-Instanz dargestellten Typ konvertieren?

jeroenh
quelle

Antworten:

133
void SetValue(PropertyInfo info, object instance, object value)
{
    info.SetValue(instance, Convert.ChangeType(value, info.PropertyType));
}
Thomas Levesque
quelle
1
Beachten Sie, dass Convert.ChangeType(value, property.PropertyType);dies immer noch fehlschlagen kann, wenn valuedie IConvertibleSchnittstelle nicht implementiert wird. Zum Beispiel, wenn info.PropertyTypees einigeIEnumerable
derekantrican
42

Die Antwort von Thomas funktioniert nur für Typen, die eine IConvertible-Schnittstelle implementieren:

Damit die Konvertierung erfolgreich ist, muss value die IConvertible-Schnittstelle implementieren, da die Methode einfach einen Aufruf einer geeigneten IConvertible-Methode umschließt. Die Methode erfordert, dass die Konvertierung von Wert in Konvertierungstyp unterstützt wird.

Dieser Code kompiliert einen linq-Ausdruck, der das Unboxing (falls erforderlich) und die Konvertierung ausführt:

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

Der resultierende Lambda-Ausdruck entspricht (TOut) (TIn) -Daten, wobei TIn der Typ der Originaldaten und TOut der angegebene Typ ist

Rafael
quelle
2
Dies ist eigentlich die Antwort, nach der ich gesucht habe. Nicht IConvertible dynamisches Gießen.
jnm2
1
Heh würde ich - wenn ich OP wäre.
jnm2
1
Ich hatte gehofft, dies würde mich retten, wenn ich versuche zu werfen IEnumerable<object>(wo diese Objekte Strings sind) IEnumerable<string>. Leider bekomme ich Fehler wieUnable to cast object of type 'System.Collections.Generic.IEnumerable'1[System.Object]' to type 'System.Collections.Generic.IEnumerable'1[System.String]'.
derekantrican
41

Die Antwort von Thomas ist richtig, aber ich dachte, ich würde meine Feststellung hinzufügen, dass Convert.ChangeType die Konvertierung in nullfähige Typen nicht handhabt. Um nullbare Typen zu behandeln, habe ich den folgenden Code verwendet:

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

Dieser Code verwendet die folgende Erweiterungsmethode:

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

Als Beitrag zu jeroenhs Antwort möchte ich hinzufügen, dass Convert.ChangeType mit einem Nullwert abstürzt. Die Zeile zum Abrufen des konvertierten Werts sollte also lauten:

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

Wenn der Typ eine Nullable Guid ist, funktioniert keine der oben vorgeschlagenen Lösungen. Es wird eine ungültige Besetzung von ' System.DBNull' bis ' System.Guid' Ausnahme ausgelöstConvert.ChangeType

So beheben Sie diese Änderung an:

var convertedValue = value == System.DBNull.Value ? null : Convert.ChangeType(value, targetType);
Loukas
quelle
2
Dieses Problem ist nicht Guid-spezifisch, sondern beruht auf der Tatsache, dass Sie nicht DBNull.Valuenur nullbeim Abrufen von Nullwerten aus der Datenbank über ADO.Net erhalten. Sie werden dasselbe zum Beispiel mit nullable int sehen.
Jeroenh
0

Dies ist eine sehr alte Frage, aber ich dachte, ich würde mich für ASP.NET Core Googler entscheiden.

Ist in ASP.NET Core .IsNullableType()(unter anderem) geschützt, sodass der Code etwas anders ist. Die Antwort von @ jeroenh wurde geändert, um in ASP.NET Core zu funktionieren:

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
quelle