仅凭System.Type将对象转换为指定类型

3
为了一个关于ObservableCollection的问题,我最近写了一个通用的扩展方法,它应该从另一个对象中加载一个对象,即将源的所有属性分配给目标,并且如果属性是引用类型,则递归地执行此操作。我使用反射做得很好,但在涉及引用类型的属性类型时遇到了问题,这是我的第一种方法:
    public static void Load<T>(this T target, T source, bool deep)
    {
        foreach (PropertyInfo property in typeof(T).GetProperties())
        {
            if (property.CanWrite && property.CanRead)
            {
                if (!deep || property.PropertyType.IsPrimitive || property.PropertyType == typeof(String))
                {
                    property.SetValue(target, property.GetValue(source, null), null);
                }
                else
                {
                    property.GetValue(target, null).Load(property.GetValue(source, null), deep);
                }
            }
        }
    }

这里的问题在于PropertyInfo.GetValue返回一个对象,接下来递归调用中T将等于object,因此我无法获取对象实际拥有的属性。
我想到了一种解决方法,需要显式传递类型,但这是非常冗余的,理论上应该可以不需要。
    public static void Load<T>(this T target, Type type, T source, bool deep)
    {
        foreach (PropertyInfo property in type.GetProperties())
        {
            if (property.CanWrite && property.CanRead)
            {
                if (!deep || property.PropertyType.IsPrimitive || property.PropertyType == typeof(String))
                {
                    property.SetValue(target, property.GetValue(source, null), null);
                }
                else
                {
                    object targetPropertyReference = property.GetValue(target, null);
                    targetPropertyReference.Load(targetPropertyReference.GetType(), property.GetValue(source, null), deep);
                }
            }
        }
    }

我也尝试使用dynamic targetPropertyReference,但是我得到了一个运行时异常,说找不到Load方法,这真让人恼火。除此之外,Convert.ChangeType也很方便地返回一个object,我似乎无法将对象转换为它本来的类型。当然,我在网上寻找答案,但迄今为止没有成功。
1个回答

2
我还没有查看完整的代码或考虑实施这种方案的全部后果(编辑:如果存在循环引用会发生什么?),但我可以为您提供两个针对您具体问题的简短修复方法:

选项1:避开这个问题

使用提供的“正常”参数的运行时类型,而不是类型参数

替换:

typeof(T).GetProperties()

with:

// You need null-checks around this:
// you can't realistically continue if target is null anyway.
target.GetType().GetProperties()

当然,这会在您的代码中引入一个轻微的语义变化,因为它会防止一些场景,例如只想复制Animal属性而不是传递Giraffe。如果sourcetarget是不同的运行时类型(具有不同的属性),它也会出现错误,但您可以轻松解决这个问题(例如找到最深层次的公共基础类型并使用它的属性)。


选项2:更多的反思/动态
当然,另一个解决方案是使用MakeGenericMethod来允许类型参数 "动态" 提供。这将保持代码的原始语义(未经测试):
typeof(MyClass).GetMethod("Load")
               .MakeGenericMethod(property.PropertyType)
               .Invoke(null, property.GetValue(target, null), 
                             property.GetValue(source, null), deep);

顺便提一下,你完全可以使用动态类型`dynamic`来实现非常相似的功能。我猜想你试图使用扩展方法调用,但是这样是不行的(链接)。请尝试正常的“静态方法”语法。

不可能!我以为我已经尝试过 target.GetType() 了,非常感谢!我想这样就可以了(我的意思是,如果某个东西被装箱了,那么它被装箱只是为了更改所有超类型特定属性的概率有多大,然后再取消装箱)。我不会将其标记为答案,因为它并没有回答实际问题(或者说它回答了吗?),但如果在一个月内没有答案出现,我会改变问题本身。 - H.B.
@H.B.:本质上,选项2会在运行时确定T应该是什么,创建一个带有类型参数的方法版本,然后调用它。 - Ani
关于你之前的评论,我知道这是这样,但我对反射不像你有经验,所以我无法判断。另外,我读了几遍后甚至理解了选项2。好吧,这是否符合“强制转换”的要求并不重要,不是吗? - H.B.
@H.B.:在这个上下文中,“casting”是什么意思还不清楚。您事先不知道属性的类型 - 这是一种固有的动态情况。 - Ani
@H.B.:如果存在循环,我真的认为这个想法会有问题。 - Ani
显示剩余6条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接