2010-08-20 20 views
227

Quiero convertir una cadena en un valor de propiedad del objeto, cuyo nombre tengo como cadena. Estoy tratando de hacerlo de esta manera:Convert.ChangeType() falla en tipos anulables

string modelProperty = "Some Property Name"; 
string value = "SomeValue"; 
var property = entity.GetType().GetProperty(modelProperty); 
if (property != null) { 
    property.SetValue(entity, 
     Convert.ChangeType(value, property.PropertyType), null); 
} 

El problema es el siguiente está fallando y lanzar una excepción moldeada no válida cuando el tipo de propiedad es un tipo anulable. Este no es el caso de los valores que no se pueden convertir: funcionarán si lo hago de forma manual (por ejemplo, DateTime? d = Convert.ToDateTime(value);). He visto algunas preguntas similares pero todavía no puedo hacer que funcionen.

+0

@dtb changetype – iboeno

+1

estoy usando ExecuteScalar con PetaPoco 4.0.3 y falla por la misma razón: retorno (T) Convert.ChangeType (val, typeof (T)) en la línea 554 – Larry

Respuesta

315

no probado, pero tal vez algo como esto va a funcionar:

string modelProperty = "Some Property Name"; 
string value = "Some Value"; 

var property = entity.GetType().GetProperty(modelProperty); 
if (property != null) 
{ 
    Type t = Nullable.GetUnderlyingType(property.PropertyType) ?? property.PropertyType; 

    object safeValue = (value == null) ? null : Convert.ChangeType(value, t); 

    property.SetValue(entity, safeValue, null); 
} 
+6

Solo necesitaba esa pieza de código. Gracias por Nullable.GetUnderlyingType! Me ayudó mucho cuando construí ModelBinder de un hombre pobre para un proyecto que lo necesitaba. ¡Te debo una cerveza! –

+3

Tal vez en lugar de '(value == null)? null' use '(value == null)? predeterminado (t) '? – threadster

55

usted tiene que conseguir el tipo subyacente con el fin de hacer eso ...

Prueba esto, he utilizado con éxito con los genéricos:

//Coalesce to get actual property type... 
Type t = property.PropertyType(); 
t = Nullable.GetUnderlyingType(t) ?? t; 

//Coalesce to set the safe value using default(t) or the safe type. 
safeValue = value == null ? default(t) : Convert.ChangeType(value, t); 

lo uso en un número de lugares en mi código, un ejemplo es un método de ayuda que utilizo para convertir los valores de base de datos de una manera typesafe:

public static T GetValue<T>(this IDataReader dr, string fieldName) 
{ 
    object value = dr[fieldName]; 

    Type t = typeof(T); 
    t = Nullable.GetUnderlyingType(t) ?? t; 

    return (value == null || DBNull.Value.Equals(value)) ? 
     default(T) : (T)Convert.ChangeType(value, t); 
} 

Llamado usando:

string field1 = dr.GetValue<string>("field1"); 
int? field2 = dr.GetValue<int?>("field2"); 
DateTime field3 = dr.GetValue<DateTime>("field3"); 

Escribí una serie de publicaciones en el blog que incluía esto en http://www.endswithsaurus.com/2010_07_01_archive.html (Desplácese hasta Addendum, @JohnMacintyre realmente detectó el error en mi código original que me llevó por el mismo camino que ahora). Tengo un par de pequeñas modificaciones desde esa publicación que incluye la conversión de tipos de enumeración, así que si su propiedad es un Enum, puede seguir utilizando la misma llamada de método. Sólo tiene que añadir una línea para revisar para este tipo de enumeración y ya está a las carreras de usar algo como:

if (t.IsEnum) 
    return (T)Enum.Parse(t, value); 

Normalmente tendrías alguna comprobación de errores o utilizar TryParse en lugar de Parse, pero se obtiene la imagen.

+0

Gracias - Soy todavía falta un paso o no entiende algo. Estoy tratando de establecer un valor de propiedad, ¿por qué recibo el objeto en el que está el Tipo subyacente? Tampoco estoy seguro de cómo pasar de mi código a un método de extensión como el tuyo. No sabré cuál será el tipo para hacer algo como value.Helper (). – iboeno

+0

@iboeno - Disculpa, estuve fuera de una reunión, así que no pude ayudarte a conectar los puntos. Me alegro de que hayas encontrado una solución. – BenAlabaster

8

Este es un largo ish poco para un ejemplo, pero esto es un enfoque relativamente robusta, y separa la tarea de fundición de valor desconocido al tipo desconocido

Tengo un método TryCast que hace algo similar y tiene en cuenta los tipos que aceptan valores de nulo.

public static bool TryCast<T>(this object value, out T result) 
{ 
    var type = typeof (T); 

    // If the type is nullable and the result should be null, set a null value. 
    if (type.IsNullable() && (value == null || value == DBNull.Value)) 
    { 
     result = default(T); 
     return true; 
    } 

    // Convert.ChangeType fails on Nullable<T> types. We want to try to cast to the underlying type anyway. 
    var underlyingType = Nullable.GetUnderlyingType(type) ?? type; 

    try 
    { 
     // Just one edge case you might want to handle. 
     if (underlyingType == typeof(Guid)) 
     { 
      if (value is string) 
      { 
       value = new Guid(value as string); 
      } 
      if (value is byte[]) 
      { 
       value = new Guid(value as byte[]); 
      } 

      result = (T)Convert.ChangeType(value, underlyingType); 
      return true; 
     } 

     result = (T)Convert.ChangeType(value, underlyingType); 
     return true; 
    } 
    catch (Exception ex) 
    { 
     result = default(T); 
     return false; 
    } 
} 

De TryCast supuesto es un método con un parámetro de tipo, por así llamarlo de forma dinámica que tiene que construir el MethodInfo mismo:

var constructedMethod = typeof (ObjectExtensions) 
    .GetMethod("TryCast") 
    .MakeGenericMethod(property.PropertyType); 

A continuación, para establecer el valor real de la propiedad:

public static void SetCastedValue<T>(this PropertyInfo property, T instance, object value) 
{ 
    if (property.DeclaringType != typeof(T)) 
    { 
     throw new ArgumentException("property's declaring type must be equal to typeof(T)."); 
    } 

    var constructedMethod = typeof (ObjectExtensions) 
     .GetMethod("TryCast") 
     .MakeGenericMethod(property.PropertyType); 

    object valueToSet = null; 
    var parameters = new[] {value, null}; 
    var tryCastSucceeded = Convert.ToBoolean(constructedMethod.Invoke(null, parameters)); 
    if (tryCastSucceeded) 
    { 
     valueToSet = parameters[1]; 
    } 

    if (!property.CanAssignValue(valueToSet)) 
    { 
     return; 
    } 
    property.SetValue(instance, valueToSet, null); 
} 

Y los métodos de extensión para tratar con property.CanAssignValue ...

public static bool CanAssignValue(this PropertyInfo p, object value) 
{ 
    return value == null ? p.IsNullable() : p.PropertyType.IsInstanceOfType(value); 
} 

public static bool IsNullable(this PropertyInfo p) 
{ 
    return p.PropertyType.IsNullable(); 
} 

public static bool IsNullable(this Type t) 
{ 
    return !t.IsValueType || Nullable.GetUnderlyingType(t) != null; 
} 
0

Gracias @LukeH
he cambiado un poco:

public static object convertToPropType(PropertyInfo property, object value) 
{ 
    object cstVal = null; 
    if (property != null) 
    { 
     Type propType = Nullable.GetUnderlyingType(property.PropertyType); 
     bool isNullable = (propType != null); 
     if (!isNullable) { propType = property.PropertyType; } 
     bool canAttrib = (value != null || isNullable); 
     if (!canAttrib) { throw new Exception("Cant attrib null on non nullable. "); } 
     cstVal = (value == null || Convert.IsDBNull(value)) ? null : Convert.ChangeType(value, propType); 
    } 
    return cstVal; 
} 
6

que tenían una necesidad similar, y la respuesta de LukeH me indicó la dirección. Se me ocurrió esta función genérica para hacerlo más fácil.

public static Tout CopyValue<Tin, Tout>(Tin from, Tout toPrototype) 
    { 
     Type underlyingT = Nullable.GetUnderlyingType(typeof(Tout)); 
     if (underlyingT == null) 
     { return (Tout)Convert.ChangeType(from, typeof(Tout)); } 
     else 
     { return (Tout)Convert.ChangeType(from, underlyingT); } 
    } 

uso es la siguiente:

 NotNullableDateProperty = CopyValue(NullableDateProperty, NotNullableDateProperty); 

Nota el segundo parámetro sólo se utiliza como un prototipo para mostrar la función de la forma de emitir el valor de retorno, por lo que en realidad no tiene que ser el propiedad de destino. Lo que significa que pueden hacer también hacer algo como esto:

 DateTime? source = new DateTime(2015, 1, 1); 
     var dest = CopyValue(source, (string)null); 

lo hice de esta manera en lugar de utilizar un fuera porque no se puede utilizar con propiedades. Como es, puede funcionar con propiedades y variables. También puede crear una sobrecarga para pasar el tipo si lo desea.

Cuestiones relacionadas