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


301

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

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

问题在于,当属性类型为可为空的类型时,这将失败并引发Invalid Cast Exception。这不是无法转换的值的情况-如果我手动执行此操作(例如DateTime? d = Convert.ToDateTime(value);),它们将起作用(我已经看到一些类似的问题,但仍然无法使它起作用)。


1
我将ExecuteScalar <int?>与PetaPoco 4.0.3一起使用时,由于相同的原因而失败:在第554行返回(T)Convert.ChangeType(val,typeof(T))
Larry

Answers:


409

未经测试,但也许这样可以工作:

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

12
我自己只需要这段代码。感谢Nullable.GetUnderlyingType!当我为需要它的项目构建一个穷人的ModelBinder时,对我有很大帮助。我欠你啤酒!
Maxime Rouiller

3
也许代替(value == null) ? null使用(value == null) ? default(t)
threadster

似乎对uniqueidentifier字符串不起作用。
安德斯·林登(AndersLindén)

创建safeValue变量而不是重新分配给变量有什么特殊的原因value吗?
coloradocolby

@threadster您不能对类型为'Type'的变量使用默认运算符。见stackoverflow.com/questions/325426/...
andy250

75

为此,您必须获取基础类型...

尝试一下,我已经成功将其与泛型一起使用:

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

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

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实际上在我的原始代码中发现了该错误,从而使我沿着与您相同的路径前进。现在)。自从该帖子还包含枚举类型的转换以来,我进行了一些小修改,因此,如果您的属性是枚举,您仍然可以使用相同的方法调用。只需添加一行以检查枚举类型,您就可以使用类似以下内容的竞赛了:

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

通常,您会进行一些错误检查,或者使用TryParse而不是Parse,但是您会得到图片。


谢谢-我仍然缺少一个步骤或不了解某些内容。我正在尝试设置属性值,为什么我得到它的底层类型的对象?我也不确定如何从我的代码获取像您这样的扩展方法。我不知道为了执行类似value.Helper <Int32?>()之类的类型。
iboeno

@iboeno-抱歉,在一次会议上参加了会议,所以我无法帮助您建立联系。很高兴您找到了解决方案。
BenAlabaster

9

举例来说,这有点长,但这是一种相对可靠的方法,将转换任务从未知值分离为未知类型

我有一个类似的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;
}

6

我也有类似的需求,卢克(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);

注意,第二个参数仅用作原型,以显示函数如何转换返回值,因此实际上不必将其作为destination属性。意味着您还可以执行以下操作:

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

我这样做是为了代替使用out,因为您不能对属性使用out。照原样,它可以使用属性和变量。如果需要,也可以创建一个重载来传递类型。


0

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

0

我是这样做的

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;
    }
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.