复杂第三方对象/类的深层复制

13

我一直在使用PDFView4Net创建PDF表单的项目中工作。虽然该库通常很好,但是在处理表单字段(如文本框、复选框等)时,表单创建器非常简陋且缺乏基本功能(例如复制/粘贴、对齐、格式化等)。

问题:我一直在扩展字段对象的功能,并在复制/粘贴上遇到了问题。为此,我需要完全不引用原始对象的深度副本。我向供应商发送了电子邮件,请求有关他们建议的复制这些对象的方法的信息,他们回复说我需要手动逐个复制每个属性…… 顶着头痛。这些都是大类,具有多个嵌入类作为属性,以及UI元素。

问题是:是否有任何良好的方法可以执行针对复杂对象的深度复制,而不需要序列化,也不需要访问或更改源类,也不需要默认构造函数?

我尝试过/审查过的内容

  • 手动逐个属性:我尝试了7个字段对象(PDFTextBoxField)中的第一个,但是由于有多个属性,这很快变得难以处理。最终,我仍然对原始对象有残留引用,因为创建的是浅复制而不是深复制。
  • 序列化:这些类未标记为可序列化,供应商也不会更改。我请求过他们,但是他们拒绝了。
  • ICloneable:需要供应商实现。
  • AutoMapper:这似乎是将一个或多个对象类型的数据复制到另一个对象类型中。我使用的对象是相同类型的。虽然如果这是最佳解决方案,我也不排斥使用它。
  • Emit Mapper:该项目似乎已停止开发。
  • MemberwiseClone:执行浅复制,而非我所需要的深度复制,尽管在许多其他帖子中建议这样做,但提问者明确要求深度复制。
  • Value Injecter:我在CodePlex上实现了ValueInjecter中的FastDeepCloneInjection,但大部分需要进行注入的类没有符合要求的0参数构造函数用于创建复制品。ValueInjecter不允许跳过某些属性,否则我将只留下未设置为null(默认值)的具有默认构造函数的项。我在第一个类中就遇到了这个问题。为了解决这个问题,我创建了一个从原始类继承的包装类,并将原始类转换为包装类(并在返回时反之),但我认为这不是一个好的解决方案。

  • 1
    那么,如何看待一个不错的反射和递归呢? - crthompson
    要使用任何类型的手动序列化(建议使用反射和递归)克隆对象,您应该首先通过FormatterServices.GetUninitializedObject(type)创建一个空实例,不要调用构造函数。 - Anders Forsgren
    也许可以参考以下链接:https://code.msdn.microsoft.com/CSDeepCloneObject-8a53311e 或 http://www.codeproject.com/Articles/38270/Deep-copy-of-objects-in-C 或 http://thomashapp.com/node/106。 - Rufus L
    我个人会为他们的每一个类创建自己的类,并继承他们的类。如果他们更新他们的库并更改他们的类,上述技术中的许多技术将失败。在您自己的类中缓解这些问题会更容易,您甚至可以添加更适合您的编辑器需求的属性。 - AaronLS
    1
    我认为这是一个相当公平的问题,并且与重复链接不同,因为他/她无法访问被克隆的类。 - wal
    1个回答

    3
    我会使用AutoMapper来完成这项任务。请考虑以下类定义:(注意私有构造函数)
    public class Parent
    {
        public string Field1 { get; set; }
        public Level1 Level1 { get; set; }
        public static Parent GetInstance()
        {
            return new Parent() { Field1 = "1", Level1 = new Level1 { Field2 = "2", Level2 = new Level2() { Field3 = "3"}}};
        }
        private Parent()  {              }
    }
    
    public class Level1
    {
        public string Field2 { get; set; }
        public Level2 Level2 { get; set; }
    }
    
    public class Level2
    {
        public string Field3 { get; set; }
    }
    

    您可以设置AutoMapper以根据需要进行深度克隆:
    [TestMethod]
    public void DeepCloneParent()
    {
        Mapper.CreateMap<Parent, Parent>();
        Mapper.CreateMap<Level1, Level1>();
        Mapper.CreateMap<Level2, Level2>();
        var parent = Parent.GetInstance();
    
        var copy = Mapper.Map<Parent, Parent>(parent);
    
        Assert.IsFalse(copy == parent);//diff object
        Assert.IsFalse(copy.Level1 == parent.Level1);//diff object
        Assert.IsFalse(copy.Level1.Level2 == parent.Level1.Level2);//diff object
        Assert.AreEqual("1", copy.Field1);
        Assert.AreEqual("2", copy.Level1.Field2);
        Assert.AreEqual("3", copy.Level1.Level2.Field3);
    }
    

    感谢您的建议。最终我成功使用AutoMapper复制了7个字段中的第一个。但我仍不确定这是否是最佳解决方案。PDFView4Net类库包含266个类,仅为了使第一个字段成功复制而编写的代码就超过了400行。尽管如此,我认为这仍然比手动完成所有操作要好得多。 - mslissap
    @mslissap 为什么有这么多行?(第一份副本涉及了多少个类?) - wal
    第一份副本有116个类。供应商使用许多“迷你”类来继承(属性为C,继承自B,继承自A,继承自基类)。一个属性可能需要4-5个类。这是我需要复制的7个对象中最简单的一个,尽管其他对象也使用了许多相同的“基础”类来继承。对于其中许多对象,我不得不创建一个ResolutionContext用于CreateMap.ConstructUsing,因为没有无参数构造函数(我将其默认为),然后创建一个AfterMap操作,以将源中的实际值放入目标中。 - mslissap
    @mslissap 哇,太糟糕了!然而我不认为你正在尝试实现的问题有一个简单/快速的解决方案。从概念上来说(比如对经理说),这似乎很容易,因为你只是想复制一个对象,但现实情况要困难得多。至少你有能力编写良好的测试来检查你的复制是否正确。 - wal

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