复制对象属性:反射还是序列化 - 哪个更快?

13
我有两个相同类型的对象,需要将一个对象的属性值复制到另一个对象。有两种选择:
  1. 使用反射,遍历第一个对象的属性并复制值。
  2. 对第一个对象进行序列化,然后反序列化出一个副本。
这两种方法都能满足我的需求,问题是哪一种在速度(成本)方面更好?
class Person
{
    public int ID { get; set; }
    public string Firsthand { get; set; } 
    public string LastName { get; set; } 
    public int Age { get; set; } 
    public decimal Weight { get; set; } 
}

需要将Person p1的属性值复制到Person p2中。

针对这个简单的示例,哪种方法更快?

更新

对于序列化,我使用了此处建议的ObjectCopier进行深度克隆。

对于反射,我使用以下代码:

foreach (PropertyInfo sourcePropertyInfo in copyFromObject.GetType().GetProperties())  
{
    PropertyInfo destPropertyInfo = copyToObject.GetType().GetProperty(sourcePropertyInfo.Name);

    destPropertyInfo.SetValue(
        copyToObject,
        sourcePropertyInfo.GetValue(copyFromObject, null),
        null);
}

2
自动映射器(http://automapper.org/)怎么样?还是只用Clone()就可以了? - roundcrisis
为什么不能使用普通赋值来完成它? - kͩeͣmͮpͥ ͩ
@Miau,这似乎是一个复杂的属性拷贝系统,感谢您提供的链接,非常有趣。但是针对我的当前需求,我需要它变得简单,因此我认为 AutoMapper 对于这种情况来说是最佳选择。但是在处理复杂任务时,我一定会仔细研究和使用它。 - net_prog
1
@DavidKemp,因为它必须是一个通用系统,适用于任何提供的类。 - net_prog
@net_prog,你想要深拷贝还是浅拷贝? - kͩeͣmͮpͥ ͩ
@DavidKemp,针对我的当前任务,我需要一个浅拷贝(只复制非引用值)。 - net_prog
5个回答

14
这完全取决于您想要复制什么,以及您计划使用什么类型的序列化程序。关于序列化程序的事情是,其中的一些可能实际上使用反射作为构建对象的基础机制。
编辑#1:据我所知,您的类使用的BinaryFormatter确实利用反射来完成其工作。因此问题是,您能否为您的类型编写更好(更快?)的自定义反射代码,而不是Microsoft为一般情况编写的代码?
编辑#2:出于好奇,我进行了简单的测试。在执行浅层复制方面,BinaryFormatter与反射相比。我使用的反射代码可以在此处看到:
var newPerson = Activator.CreateInstance<Person>();
var fields = newPerson.GetType().GetFields(BindingFlags.Public 
    | BindingFlags.Instance);
foreach (var field in fields)
{
    var value = field.GetValue(person);
    field.SetValue(newPerson, value);
}

与您使用的ObjectCopier类相比,反射似乎执行得更快,速度快了7倍。然而,这仅适用于具有公共字段的Person类。对于属性来说,差异仍然是可察觉的,但只快了2倍。
我认为差异来自于BinaryFormatter需要使用流,这会引入额外的开销。然而,这只是我的假设,可能与事实相去甚远。
测试程序的源代码可以在此处找到。欢迎任何人指出其中的缺陷和可能存在的问题 :-)

旁注
与所有"我在想..."基准测试一样,建议您持保留态度。只有在性能真正成为问题时才应进行此类优化。


我有一些包含基本类型(如int、string等)的简单类...我使用这里建议的序列化器:https://dev59.com/H3VD5IYBdhLWcg3wHn6d 类的类型由外部代码提供。 - net_prog
@net_prog:请检查我的编辑。我已经使用两种方法对你的类进行了简单测试。 - k.m
谢谢,这正是我想要的。实际上,我希望类具有属性而不是字段,我已经更正了示例。但即使在这种情况下,反射也更快,我可以看到。 - net_prog
如果您在GetFields()的搜索选项中指定System.Reflection.BindingFlags.Instance|System.Reflection.BindingFlags.NonPublic,它将包括所有属性的后备字段。 - Suncat2000

8
最终,通用序列化程序(例如通过ObjectCopier使用BinaryFormatter)采用反射。它们对其使用情况的好坏取决于特定的序列化程序,但如果您进行序列化,则始终会涉及额外的开销。
由于您只需要浅复制,因此在这里AutoMapper是最合适的工具;同样,它使用反射(但我希望它是“正确的”方式,即不通过GetValue() / SetValue()),但它没有序列化成本。
在这种情况下,序列化是过度的;AutoMapper是完全合理的。如果你想要深克隆,那就更棘手了……序列化可能开始变得有吸引力。我仍然可能不会选择BinaryFormatter,但我对序列化非常挑剔;p 当然,编写一些基本的反射代码以通过GetValue()等执行相同的操作是微不足道的,但速度很慢。另一个有趣的选择是,您可以使用Expression API在运行时创建对象复制程序...但是...AutoMapper在这里完成了您需要的一切,因此看起来是多余的努力。

0

我使用该线程中的ObjectCopier进行序列化,比起反射更快吗? - net_prog
3
你认为 BinaryFormatterObjectCopier 下的序列化程序)是如何获取数据的?提示:它以“r”开头,发音类似于“偏转”。 - Marc Gravell

0
如果您需要在运行时复制属性,则反射是最好的选择。如果不是在运行时,我会选择序列化。 序列化 vs 反射 点击此处查看。

如果您在运行时知道对象的属性,则反射将是浪费时间,序列化将是答案。但是,如果您在运行时不知道对象的属性,则反射是我能想到的唯一解决方案。 - nebula
如果您已经了解属性,为什么序列化会突然比反射更好呢?不确定这是否有意义... - Marc Gravell
因为在运行时不知道对象的类型时,才会使用反射。如果已知对象的类型,为什么要费心使用反射,而序列化(二进制序列化)又快又容易实现呢? - nebula
1
这根本没有任何意义。序列化在这里将涉及反射; 添加实际的编码/解码步骤只会增加工作量。对于反射复制器与序列化的两种合理实现以及浅拷贝,反射复制器在各个方面都更可取。这是一个对反射和序列化都非常了解的人所说的话。 - Marc Gravell

0
void Copy(object copyToObject, object copyFromObject)
{
    BindingFlags flags = BindingFlags.Public | BindingFlags.Instance;

    FieldInfo[] fields = copyFromObject.GetType().GetFields(flags);
    for (int i = 0; i < fields.Length; ++i)
    {
        BindingFlags bindFlags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic
            | BindingFlags.Static;
        FieldInfo field = copyFromObject.GetType().GetField(fields[i].Name, bindFlags);
        FieldInfo toField = copyToObject.GetType().GetField(fields[i].Name, bindFlags);
        if(field != null)
        {
            toField.SetValue(copyToObject, field.GetValue(copyFromObject));
        }
    }
}

代码可能会回答问题。但如果您解释代码的作用以及如何解决问题,那么您将更有助于帮助提问者。 - Markus

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