Convert.ChangeType () falla en tipos anulables

Quiero convertir una cadena en un valor de propiedad del objeto, cuyo nombre tengo como cadena. Estoy tratando de hacer esto así:

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 que está fallando y lanzando una excepción de lanzamiento inválido cuando el tipo de propiedad es un tipo que admite nulos. Este no es el caso de los valores que no se pueden convertir. DateTime? d = Convert.ToDateTime(value); si lo hago manualmente (por ejemplo, DateTime? d = Convert.ToDateTime(value); ) He visto algunas preguntas similares, pero todavía no puedo obtenerlas. trabajar.

No probado, pero tal vez algo como esto 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); } 

Debes obtener el tipo subyacente para poder hacer eso …

Prueba esto, lo he usado con éxito con generics:

 //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 varios lugares en mi código, un ejemplo es un método de ayuda que utilizo para convertir los valores de la base de datos de una manera segura:

 public static T GetValue(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("field1"); int? field2 = dr.GetValue("field2"); DateTime field3 = dr.GetValue("field3"); 

Escribí una serie de publicaciones en el blog, incluida esta en http://www.endswithsaurus.com/2010_07_01_archive.html (Desplácese hacia abajo, Addendum, @JohnMacintyre realmente detectó el error en mi código original que me llevó por el mismo camino que está 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. Simplemente agregue una línea para verificar los tipos enum y estará listo para las carreras usando algo como:

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

Normalmente tendrías algún error al verificar o usar TryParse en lugar de Parse, pero obtienes la imagen.

Esto es un poco largo para un ejemplo, pero este es un enfoque relativamente robusto, y separa la tarea de fundición de un valor desconocido a un tipo desconocido.

Tengo un método TryCast que hace algo similar, y toma en cuenta los tipos que aceptan nulos.

 public static bool TryCast(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 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; } } 

Por supuesto, TryCast es un método con un parámetro de tipo, por lo que para llamarlo dinámicamente debe construir usted mismo MethodInfo:

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

Luego, para establecer el valor real de la propiedad:

 public static void SetCastedValue(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; } 

Tenía 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 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); } } 

El uso es así:

  NotNullableDateProperty = CopyValue(NullableDateProperty, NotNullableDateProperty); 

Tenga en cuenta que el segundo parámetro solo se utiliza como prototipo para mostrar la función de cómo convertir el valor de retorno, por lo que no tiene que ser la propiedad de destino. Lo que significa que también puedes 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 usar una salida porque no puedes usar las propiedades. Como es, puede funcionar con propiedades y variables. También puede crear una sobrecarga para pasar el tipo si lo desea.

Gracias @LukeH
Cambié 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; }