'casting' con reflejo

81

Considere el siguiente código de muestra:

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

Ahora necesito hacer algo similar a través de la reflexión:

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

Tenga en cuenta que no puedo asumir que PropertyInfo siempre representa un valor largo, ni ese valor es siempre un decimal. Sin embargo, sé que el valor se puede convertir al tipo correcto para esa propiedad.

¿Cómo puedo convertir el parámetro 'valor' al tipo representado por la instancia PropertyInfo a través de la reflexión?

jeroenh
fuente

Respuestas:

133
void SetValue(PropertyInfo info, object instance, object value)
{
    info.SetValue(instance, Convert.ChangeType(value, info.PropertyType));
}
Thomas Levesque
fuente
1
Tenga en cuenta que Convert.ChangeType(value, property.PropertyType);aún puede fallar si valueno implementa la IConvertibleinterfaz. Por ejemplo, si info.PropertyTypees algoIEnumerable
derekantrican
42

La respuesta de Thomas solo funciona para los tipos que implementan la interfaz IConvertible:

Para que la conversión tenga éxito, value debe implementar la interfaz IConvertible, porque el método simplemente envuelve una llamada a un método IConvertible apropiado. El método requiere que se admita la conversión de valor a conversionType.

Este código compila una expresión linq que realiza el desempaquetado (si es necesario) y la conversión:

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

La expresión lambda resultante es igual a (TOut) (TIn) Data donde TIn es el tipo de datos originales y TOut es el tipo dado

Rafael
fuente
2
Esta es en realidad la respuesta que vine a buscar. Fundición dinámica no convertible.
jnm2
1
Je, lo haría, si fuera OP.
jnm2
1
Esperaba que esto me salvara al intentar lanzar IEnumerable<object>(donde esos objetos son cadenas) a IEnumerable<string>. Desafortunadamente, recibo errores comoUnable to cast object of type 'System.Collections.Generic.IEnumerable'1[System.Object]' to type 'System.Collections.Generic.IEnumerable'1[System.String]'.
derekantrican
41

La respuesta de Thomas es correcta, pero pensé que agregaría mi hallazgo de que Convert.ChangeType no maneja la conversión a tipos que aceptan valores NULL. Para manejar tipos que aceptan valores NULL, utilicé el siguiente código:

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

Este código utiliza el siguiente método de extensión:

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

Contribuyendo a la respuesta de jeroenh, agregaría que Convert.ChangeType se bloquea con un valor nulo, por lo que la línea para obtener el valor convertido debería ser:

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

Cuando el tipo es una guía anulable, ninguna de las soluciones propuestas anteriormente funciona. Se lanza una excepción de conversión no válida de ' System.DBNull' a ' System.Guid'Convert.ChangeType

Para corregir ese cambio en:

var convertedValue = value == System.DBNull.Value ? null : Convert.ChangeType(value, targetType);
Loukas
fuente
2
Este problema no es específico de Guid, sino que se debe al hecho de que obtiene en DBNull.Valuelugar de simplemente nullcuando obtiene valores nulos de la base de datos a través de ADO.Net. Verá lo mismo con nullable int, por ejemplo.
jeroenh
0

Esta es una pregunta muy antigua, pero pensé en intervenir para ASP.NET Core Googlers.

En ASP.NET Core, .IsNullableType()está protegido (entre otros cambios), por lo que el código es un poco diferente. Aquí está la respuesta de @ jeroenh modificada para funcionar en 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
fuente