简体   繁体   English

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

[英]Convert.ChangeType() fails on Nullable Types

I want to convert a string to an object property value, whose name I have as a string.我想将字符串转换为对象属性值,我将其名称作为字符串。 I am trying to do this like so:我正在尝试这样做:

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

The problem is this is failing and throwing an Invalid Cast Exception when the property type is a nullable type.问题是当属性类型是可空类型时,这会失败并抛出无效的强制转换异常。 This is not the case of the values being unable to be Converted - they will work if I do this manually (eg DateTime? d = Convert.ToDateTime(value); ) I've seen some similiar questions but still can't get it to work.这不是无法转换值的情况 - 如果我手动执行此操作,它们将起作用(例如DateTime? d = Convert.ToDateTime(value); )我已经看到了一些类似的问题,但仍然无法得到它上班。

Untested, but maybe something like this will work:未经测试,但也许这样的事情会奏效:

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

You have to get the underlying type in order to do that...你必须获得底层类型才能做到这一点......

Try this, I've used it successfully with 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);

I use it in a number of places in my code, one example is a helper method I use for converting database values in a typesafe manner:我在代码中的很多地方都使用了它,一个例子是我用来以类型安全的方式转换数据库值的辅助方法:

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

Called using:调用使用:

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

I wrote a series of blog posts including this at http://www.endswithsaurus.com/2010_07_01_archive.html (Scroll down to the Addendum, @JohnMacintyre actually spotted the bug in my original code which led me down the same path you're on now).我写了一系列博客文章,包括在http://www.endswithsaurus.com/2010_07_01_archive.html (向下滚动到附录, @JohnMacintyre实际上在我的原始代码中发现了错误,这导致我走上了与您相同的道路现在)。 I have a couple of small modifications since that post that includes conversion of enum types also so if your property is an Enum you can still use the same method call.自从那篇文章还包括枚举类型的转换后,我有一些小的修改,所以如果您的属性是枚举,您仍然可以使用相同的方法调用。 Just add a line in to check for enum types and you're off to the races using something like:只需添加一行以检查枚举类型,您就可以使用以下内容参加比赛:

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

Normally you'd have some error checking or use TryParse instead of Parse, but you get the picture.通常你会进行一些错误检查或使用 TryParse 而不是 Parse,但你会得到图片。

This is a little bit long-ish for an example, but this is a relatively robust approach, and separates the task of casting from unknown value to unknown type这对于一个例子来说有点长,但这是一种相对健壮的方法,并将从未知值转换为未知类型的任务分开

I have a TryCast method that does something similar, and takes nullable types into account.我有一个 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;
    }
}

Of course TryCast is a Method with a Type Parameter, so to call it dynamically you have to construct the MethodInfo yourself:当然 TryCast 是一个带有类型参数的方法,因此要动态调用它,您必须自己构造 MethodInfo:

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

Then to set the actual property value:然后设置实际的属性值:

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

And the extension methods to deal with property.CanAssignValue...以及处理 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;
}

I had a similar need, and the answer from LukeH pointed me in the direction.我有类似的需求,LukeH 的回答为我指明了方向。 I came up with this generic function to make it easy.我想出了这个通用函数来简化它。

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

Usage is like this:用法是这样的:

        NotNullableDateProperty = CopyValue(NullableDateProperty, NotNullableDateProperty);

Note the second parameter is just used as a prototype to show the function how to cast the return value, so it doesn't actually have to be the destination property.请注意,第二个参数仅用作原型来显示函数如何转换返回值,因此它实际上不必是目标属性。 Meaning you can do also do something like this:这意味着你也可以做这样的事情:

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

I did it this way instead of using an out because you can't use out with properties.我是这样做的,而不是使用 out ,因为您不能使用 out 属性。 As is, it can work with properties and variables.照原样,它可以处理属性和变量。 You could also create an overload to pass the type instead if you wanted.如果需要,您还可以创建一个重载来传递类型。

This works perfectly even for Nullable types:这甚至适用于 Nullable 类型:

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

For type safety you should also call conv.CanConvertFrom(type) method before calling ConvertFrom() .为了类型安全,您还应该在调用ConvertFrom()之前调用conv.CanConvertFrom(type)方法。 In case it returns false you can fallback to ChangeType or something else.如果它返回 false,您可以回ChangeType或其他东西。

I did it in this way我是这样做的

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

Thanks @LukeH谢谢@LukeH
I changed a little:我改变了一点:

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM