Convert.ChangeType() fails on Nullable Types
我想将一个字符串转换为一个对象属性值,它的名称是字符串。我想这样做:
1 2 3 4 5 6 7 | 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); } |
问题是,当属性类型为可为空的类型时,这将失败并引发无效的强制转换异常。这不是无法转换值的情况-如果我手动转换(例如
未经测试,但这样的方法可能有效:
1 2 3 4 5 6 7 8 9 10 11 12 | 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); } |
要做到这一点,您必须获取底层类型…
尝试一下,我已经成功地将它与泛型一起使用了:
1 2 3 4 5 6 | //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); |
我在代码中的许多地方使用它,一个例子是我用于以类型安全的方式转换数据库值的助手方法:
1 2 3 4 5 6 7 8 9 10 | 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); } |
调用使用:
1 2 3 | 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上写了一系列的博客文章,其中包括这个(向下滚动到附录,@johnmacintire实际上在我的原始代码中发现了这个bug,它引导我走上了与你现在相同的道路)。我有一些小的修改,因为那篇文章包括枚举类型的转换,所以如果你的属性是一个枚举,你仍然可以使用相同的方法调用。只需添加一行来检查枚举类型,您就可以使用如下方法进入竞赛:
1 2 | if (t.IsEnum) return (T)Enum.Parse(t, value); |
通常情况下,您会进行一些错误检查,或者使用typarse而不是parse,但是您会得到图片。
例如,这有点长,但这是一种相对健壮的方法,它将强制转换任务从未知值分离到未知类型。
我有一个Trycast方法,它做了类似的事情,并且考虑了可以为空的类型。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 | 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是一个带有类型参数的方法,因此要动态调用它,您必须自己构造MethodInfo:
1 2 3 | var constructedMethod = typeof (ObjectExtensions) .GetMethod("TryCast") .MakeGenericMethod(property.PropertyType); |
然后设置实际属性值:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | 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); } |
以及处理属性的扩展方法。canassignvalue…
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | 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; } |
我也有同样的需要,卢克的回答也向我指明了方向。我想出了这个通用函数来简化它。
1 2 3 4 5 6 7 8 |
用法如下:
1 | NotNullableDateProperty = CopyValue(NullableDateProperty, NotNullableDateProperty); |
注意,第二个参数只是作为一个原型来显示函数如何转换返回值,因此它实际上不必是destination属性。这意味着你也可以这样做:
1 2 |
我这样做而不是使用out,因为你不能使用out和属性。实际上,它可以处理属性和变量。如果需要,还可以创建一个重载来传递类型。
我是这样做的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | public static List<T> Convert<T>(this ExcelWorksheet worksheet) where T : new() { var result = new List<T>(); int colCount = worksheet.Dimension.End.Column; //get Column Count int rowCount = worksheet.Dimension.End.Row; for (int row = 2; row <= rowCount; row++) { var obj = new T(); for (int col = 1; col <= colCount; col++) { var value = worksheet.Cells[row, col].Value?.ToString(); PropertyInfo propertyInfo = obj.GetType().GetProperty(worksheet.Cells[1, col].Text); propertyInfo.SetValue(obj, Convert.ChangeType(value, Nullable.GetUnderlyingType(propertyInfo.PropertyType) ?? propertyInfo.PropertyType), null); } result.Add(obj); } return result; } |
谢谢@ LukeH我改变了一点:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | 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; } |