Deep Copy of Complex Third Party Objects/Classes [duplicate]
Asked Answered
B

1

13

I'm have been working on a project to create PDF forms using PDFView4Net. While the library is generally good, the forms creator is primitive and lacking basic features (such as copy/paste, alignment, formatting, etc.) when working with form fields (i.e. text boxes, check boxes, etc.).

The problem: I have been extending the functionality for field objects and getting tripped up on copy/paste. To do this, I need a deep copy of the object with no references to the original whatsoever. I emailed the vendor, requesting information about their recommended method for copying these objects, to which they replied I needed make a copy of each property by hand, manually ... beats head on desk. These are large classes, with multiple embedded classes as properties, as well as UI elements.

The question: Are there any good methods out there that perform a deep copy for complex objects that don't require serialization, doesn't require access to or changes to the source classes and doesn't require a default constructor?

What I have tried/reviewed: I have researched various ways to make a deep copy of an object and discarded them one by one:

  • Manually, Property by Painstaking Property: I attempted this with the first of 7 field objects (PDFTextBoxField), but it quickly got out of hand with the multitude properties that are also different types of classes. In the end, I still had lingering references to the original object where it a shallow copy was created instead of a deep copy as intended.
  • Serialization: The classes are not marked as Serializable, nor will the vendor change this. I asked them to and they said no.
  • ICloneable: Would need to be implemented by the vendor.
  • AutoMapper: This seems to be for copying data from one or more object types into another object type. The objects I'm working with are the same type. Though I'm not opposed to using this if it is the best solution.
  • Emit Mapper: This project appears to have been abandoned.
  • MemberwiseClone: Does a shallow copy, not the deep copy I'm looking for, though this is suggested on a ton of other posts when the questioner specifically requests a deep copy.
  • Value Injecter: I implemented FastDeepCloneInjection from ValueInjecter on CodePlex but the majority of the classes that need to be injected from do not have a 0 parameter constructor which is required when creating a new instance for the copy. ValueInjecter doesn't allow skipping of certain properties, or I would just skip the items with no default constructor and leave them set to null (the default). I ran into this right away with the very first class. To attempt to get around the issue, I created a wrapper class inherited from the original and cast the original into the wrapper (and vice versa on return), but I don't think that's a good solution.

Edit: I really do not feel this question is a duplicate. I've searched extensively for a solution, including the post marked as the duplicate/original, and was unable to find a satisfactory resolution. As stated, I don't have access to change the classes that I need to copy. This discounts DataContractSerializer, BinaryFormatter, and any other type of serialization. This also discounts the reflection examples I've seen using Activator.CreateInstance, as about 95% of the classes I need to copy don't have a constructor that takes 0 arguments. This is the same issue I ran into using ValueInjecter. This also discounts using ICloneable.

Baptize answered 25/2, 2015 at 21:11 Comment(5)
How about a nice reflection and recursion?Etoile
To clone an object with any kind of hand-rolled serialization (reflection and recursion as suggested) you should start by just creating a blank instance without constructor call, via FormatterServices.GetUninitializedObject(type).Keyes
Maybe something like:code.msdn.microsoft.com/CSDeepCloneObject-8a53311e or codeproject.com/Articles/38270/Deep-copy-of-objects-in-C or thomashapp.com/node/106Evincive
I'd personally create my own class for each of theirs, and inherit theirs. Many of the above techniques will fail if they update their library and change their classes. It'll be easier to mitigate these problems in your own classes, and you can even add properties that better suit the needs of your editor.Turenne
I think this is a fair enough question and different from the dup. link insofar as he/she doesnt have access to the classes being clonedEckhart
E
3

I would use AutoMapper for this. Consider the following class definition: (note private ctor)

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

You can then setup AutoMapper to deep clone as required:

[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);
}
Eckhart answered 25/2, 2015 at 21:52 Comment(4)
Thanks for the recommendation. I was finally able to get the first of 7 fields to copy successfully using AutoMapper. I'm still not sure this is the best solution though. The PDFView4Net class library contains 266 classes. It took over 400 lines of code just to get that first one to copy without error. Still, I guess it's better than doing it all manually.Baptize
@Baptize why so many lines ?? (how many classes were involved in that first copy?)Eckhart
116 classes for the 1st copy. The vendor uses a lot of "mini" classes to inherit from (property is C, inherits from B, inherits from A, inherits from base). There could be 4-5 classes required for just one property. This was the simplest one of the 7 objects I need to copy, though the others use a lot of the same "base" classes to inherit from. For many of those I had to create a ResolutionContext for CreateMap.ConstructUsing since there was no parameterless constructor (I defaulted these), then create an AfterMap action to get the real value out of the source and into the destination.Baptize
@Baptize woah nasty! However i do not believe there is an easy/quick solution to what you are trying to achieve. Conceptually (say to a manager!) it seems easy as you just want to copy an object but the reality is much more difficult. At least you have the ability to write good tests to check your copying is correct.Eckhart

© 2022 - 2024 — McMap. All rights reserved.