web-dev-qa-db-fra.com

«coulée» avec réflexion

Considérez l'exemple de code suivant:

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

Maintenant, je dois faire quelque chose de similaire à travers la réflexion:

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

Notez que je ne peux pas supposer que le PropertyInfo représente toujours un long, ni que cette valeur est toujours une décimale. Cependant, je sais que la valeur peut être castée dans le type correct pour cette propriété.

Comment puis-je convertir le paramètre 'value' en type représenté par l'instance PropertyInfo par réflexion?

75
jeroenh
void SetValue(PropertyInfo info, object instance, object value)
{
    info.SetValue(instance, Convert.ChangeType(value, info.PropertyType));
}
126
Thomas Levesque

La réponse de Thomas est juste, mais j'ai pensé ajouter ma conclusion selon laquelle Convert.ChangeType ne gère pas la conversion en types nullables. Pour gérer les types nullables, j'ai utilisé le code suivant:

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

Ce code utilise la méthode d'extension suivante:

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

Thomas answer ne fonctionne que pour les types qui implémentent une interface IConvertible:

Pour que la conversion réussisse, la valeur doit implémenter l'interface IConvertible, car la méthode encapsule simplement un appel à une méthode IConvertible appropriée. La méthode nécessite que la conversion de valeur en conversionType soit prise en charge.

Ce code compile une expression linq qui fait le déballage (si nécessaire) et la 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;
    }

L'expression lambda résultante est égale à (TOut) (TIn) Data où TIn est le type des données d'origine et TOut est le type donné

37
rafael

En contribuant à la réponse de jeroenh, j'ajouterais que Convert.ChangeType se bloque avec une valeur nulle, donc la ligne pour obtenir la valeur convertie devrait être:

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

Lorsque le type est un guide nul, aucune des solutions proposées ci-dessus ne fonctionne. Cast non valide de 'System.DBNull' à 'System.Guid 'exception est levée sur Convert.ChangeType

Pour corriger ce changement en:

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

C'est une très vieille question, mais je pensais que je ferais sonner pour les Googlers ASP.NET Core.

Dans ASP.NET Core, .IsNullableType() est protégé (entre autres modifications), donc le code est un peu différent. Voici la réponse de @ jeroenh modifiée pour fonctionner dans 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);
}
0
chakeda