反思“铸造”


81

考虑以下示例代码:

class SampleClass
{
    public long SomeProperty { get; set; }
}

public void SetValue(SampleClass instance, decimal value)
{
    // value is of type decimal, but is in reality a natural number => cast
    instance.SomeProperty = (long)value;
}

现在,我需要通过反射来做类似的事情:

void SetValue(PropertyInfo info, object instance, object value)
{
    // throws System.ArgumentException: Decimal can not be converted to Int64
    info.SetValue(instance, value)  
}

请注意,我不能假设PropertyInfo始终表示一个long,该值也不总是十进制。但是,我知道可以将值强制转换为该属性的正确类型。

如何通过反射将'value'参数转换为PropertyInfo实例表示的类型?

Answers:


133
void SetValue(PropertyInfo info, object instance, object value)
{
    info.SetValue(instance, Convert.ChangeType(value, info.PropertyType));
}

1
请注意,Convert.ChangeType(value, property.PropertyType);如果value不实现该IConvertible接口,仍然会失败。例如,如果info.PropertyType有的话IEnumerable
derekantrican

42

Thomas答案仅适用于实现IConvertible接口的类型:

为使转换成功,值必须实现IConvertible接口,因为该方法只是将对一个合适的IConvertible方法的调用包装起来。该方法要求支持将值转换为conversionType。

这段代码编译一个linq表达式,该表达式执行拆箱(如果需要)和转换:

    public static object Cast(this Type Type, object data)
    {
        var DataParam = Expression.Parameter(typeof(object), "data");
        var Body = Expression.Block(Expression.Convert(Expression.Convert(DataParam, data.GetType()), Type));

        var Run = Expression.Lambda(Body, DataParam).Compile();
        var ret = Run.DynamicInvoke(data);
        return ret;
    }

所得的lambda表达式等于(TOut)(TIn)Data,其中TIn是原始数据的类型,TOut是给定的类型


2
这实际上是我要寻找的答案。非IConvertible动态铸造。
jnm2

1
嘿,我会的-如果我是OP。
jnm2

1
希望在尝试将IEnumerable<object>(这些对象为字符串)转换为时可以为我节省IEnumerable<string>。不幸的是,我遇到类似Unable to cast object of type 'System.Collections.Generic.IEnumerable'1[System.Object]' to type 'System.Collections.Generic.IEnumerable'1[System.String]'.
derekantrican的

41

托马斯的答案是正确的,但我想我会添加我的发现,即Convert.ChangeType不能处理可空类型的转换。为了处理可空类型,我使用了以下代码:

void SetValue(PropertyInfo info, object instance, object value)
{
    var targetType = info.PropertyType.IsNullableType() 
         ? Nullable.GetUnderlyingType(info.PropertyType) 
         : info.PropertyType; 
    var convertedValue = Convert.ChangeType(value, targetType);

    info.SetValue(instance, convertedValue, null);
}

此代码使用以下扩展方法:

public static class TypeExtensions
{
  public static bool IsNullableType(this Type type)
  {
    return type.IsGenericType 
    && type.GetGenericTypeDefinition().Equals(typeof(Nullable<>));
  }

10

有助于jeroenh的答案,我会添加Convert.ChangeType崩溃并带有空值,因此获取转换后的值的行应为:

var convertedValue = value == null ? null : Convert.ChangeType(value, targetType);

2

当类型为可为空的Guid时,以上建议的解决方案均无效。从“ System.DBNull”到“ System.Guid”的强制转换无效抛出Convert.ChangeType

将该更改修复为:

var convertedValue = value == System.DBNull.Value ? null : Convert.ChangeType(value, targetType);

2
此问题不是Guid特有的,而是由于您通过ADO.Net从数据库中获取空值DBNull.Value而不是简单地得到该事实null。例如,您将看到具有nullable int的内容。
jeroenh

0

这是一个非常老的问题,但是我认为我很喜欢ASP.NET Core Googlers。

在ASP.NET Core中,它.IsNullableType()是受保护的(除了其他更改),因此代码略有不同。这是@jeroenh的答案,修改后可以在ASP.NET Core中使用:

void SetValue(PropertyInfo info, object instance, object value)
{
    Type proptype = info.PropertyType;
    if (proptype.IsGenericType && proptype.GetGenericTypeDefinition().Equals(typeof(Nullable<>)))
    {
        proptype = new NullableConverter(info.PropertyType).UnderlyingType;
    }

    var convertedValue = Convert.ChangeType(value, proptype);
    info.SetValue(instance, convertedValue);
}
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.