Convert.ChangeType()在可空类型上失败

我想将一个字符串转换为对象属性值,其名称为字符串。我试着这样做:

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

问题是,当属性类型是可空类型时,这将失败并抛出无效强制转换异常。这不是无法转换值的情况-如果我手动这样做(例如DateTime? d = Convert.ToDateTime(value);),它们会工作。我看到过一些类似的问题,但仍然不能让它工作。

88874 次浏览

为了做到这一点,你必须获得底层类型……

试试这个,我已经成功地使用了泛型:

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

我在代码中的许多地方使用了它,其中一个例子是我使用的helper方法,用于以类型安全的方式转换数据库值:

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实际上发现了我的原始代码中的错误,这使我走上了你现在正在走的同一条路)。自从那篇文章以来,我有一些小的修改,包括枚举类型的转换,所以如果你的属性是enum,你仍然可以使用相同的方法调用。只需添加一行来检查枚举类型,你就可以使用如下代码来竞争:

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

通常情况下,您会进行一些错误检查或使用TryParse而不是Parse,但您已经了解了情况。

未经测试,但也许这样的东西会起作用:

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

这对于一个示例来说有点长,但是这是一个相对健壮的方法,并且将转换任务从未知值分离到未知类型

我有一个TryCast方法,它做了类似的事情,并考虑了可空类型。

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:

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;
}
< p >谢谢@LukeH < br > 我改变了一点:

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

我也有类似的需求,而LukeH的回答为我指明了方向。我用这个通用函数来简化。

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

我用这种方法代替了out因为你不能在属性中使用out。实际上,它可以使用属性和变量。如果需要,也可以创建重载来传递该类型。

我是这样做的

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

这甚至适用于Nullable类型:

TypeConverter conv = TypeDescriptor.GetConverter(type);
return conv.ConvertFrom(value);

为了类型安全,你还应该在调用ConvertFrom()之前调用conv.CanConvertFrom(type)方法。如果它返回false,你可以回退到ChangeType或其他东西。