Convert.ChangeType()在可空types上失败

我想将string转换为对象属性值,其名称作为string。 我正在尝试这样做:

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

问题是这是失败,并且当属性types是可为空的types时抛出一个无效的转换exception。 这不是值不能被转换的情况下 – 他们将工作,如果我手动(例如DateTime? d = Convert.ToDateTime(value); )我已经看到一些类似的问题,但仍然无法得到它上class。

未经testing,但也许这样的事情将工作:

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

你必须得到底层types才能做到这一点…

试试这个,我用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); 

我在我的代码中的许多地方使用它,一个例子是我用于types安全的方式转换数据库值的帮助器方法:

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

调用使用:

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

我在http://www.endswithsaurus.com/2010_07_01_archive.html上写了一系列的博客文章(向下滚动到附录,; @JohnMacintyre实际上发现了我原来的代码中的错误,现在)。 自从那篇文章中,我有一些小的修改,包括枚举types的转换,所以如果你的属性是一个枚举,你仍然可以使用相同的方法调用。 只需添加一行来检查枚举types,然后使用类似下面的方法进行比赛:

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

通常你会有一些错误检查或使用TryParse而不是parsing,但你得到的图片。

这个例子有点长,但是这是一个相对稳健的方法,把铸造的任务从未知值分离到未知types

我有一个类似TryCast方法,并考虑到可空types。

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

当然,TryCast是一个带有types参数的方法,所以要dynamic地调用它,你必须自己构造MethodInfo:

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

然后设置实际的属性值:

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

和扩展方法来处理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; } 

我也有类似的需要,卢克的回答指出了我的方向。 我想出了这个通用函数来简化它。

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

用法如下所示:

  NotNullableDateProperty = CopyValue(NullableDateProperty, NotNullableDateProperty); 

注意第二个参数只是作为一个原型来显示函数如何转换返回值,所以它实际上并不一定是目标属性。 意思是你也可以做这样的事情:

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

我这样做,而不是用掉,因为你不能使用属性。 因为它可以处理属性和variables。 你也可以创build一个重载来传递types,而不是你想要的。

谢谢@LukeH
我改变了一点:

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