Search code examples
c#clone

How to Create True Deep Copies in C# Without Serialization?


I am reading the book "Dingle, A. (2021). Chapter: Cloning in C#. In Object-oriented design choices (pp. 71–73), CRC Press." and in the chapter about Cloning in C# it shows how to make a deep and a shallow copy of an object through the UCopy class.

I am having trouble understanding how all this works. I found some blogs online and it says a true deep copy is hard to make in C# (without the Serialize and Deserialize method). I am working on implementing this in my assignment and I can't wrap my head around the process.

  • Do I need a UCopy class for every class I make (Is it made to copy one class only, or should it be generic)
  • Does it create a true deep copy? I could be wrong, but to my understanding u4 is not a true deep copy because, as in u3, the AnotherClass object is distinct, but the Own value is not truly distinct as it's initialized from the same static id.
  • Where does the Clone method come in?

Here is the code example from the book (FIGURE 3.5)

public class AnotherClass
{
    private static uint id = 1000;
    private readonly uint own;

    public AnotherClass()
    {
        own = id++;
    }

    public uint Own => own;
}

public class UCopy
{
    private AnotherClass address;

    public UCopy()
    {
        address = new AnotherClass();
    }

    // Deep copy: heap memory allocated for a true copy
    public UCopy DeepCopy()
    {
        // Internal cast
        UCopy local = (UCopy)this.MemberwiseClone();
        local.address = new AnotherClass();
        return local;
    }

    // Shallow copy: distinct objects have the same address
    public UCopy ShallowCopy()
    {
        return (UCopy)this.MemberwiseClone();
    }
}

public class Program
{
    public static void Main()
    {
        // Client code
        // #1: UCopy object allocated
        UCopy u1 = new UCopy();

        // Embedded 'Own' id of 1000

        // #2: Shallow copy, aliased with u1
        UCopy u2 = u1.ShallowCopy();

        // Embedded 'Own' id of 1000

        // #3: Shallow copy, aliased with u1
        UCopy u3 = u1.DeepCopy();

        // Embedded 'Own' id of 1000

        // #4: Deep copy - DISTINCT object
        UCopy u4 = u1.DeepCopy();

        // Embedded 'Own' id of 1001
    }
}

Solution

  • You run down a deep rabbit hole if use MemberwiseClone() as it really doesn't make it clear what you need to modify to make a deep clone.

    You're better off writing our own deep clone method and explicitly update everything.

    Here's an example:

    public class Class1
    {
        public Class2 Class2 { get; init; }
        public string Id { get; set; }
    
        public Class1 DeepCopy() => new Class1()
        {
            Id = this.Id,
            Class2 = this.Class2.DeepCopy()
        };
    }
    
    public class Class2
    {
        public Class3 Class3 { get; init; }
        public string Id { get; set; }
    
        public Class2 DeepCopy() => new Class2()
        {
            Id = this.Id,
            Class3 = this.Class3.DeepCopy()
        };
    }
    
    public class Class3
    {
        public string Id { get; set; }
        public Class3 DeepCopy() => new Class3()
        {
            Id = this.Id
        };
    }
    

    I think you're better off, though, writing static method on each class to do the cloning:

    public class Class1
    {
        public Class2 Class2 { get; init; }
        public string Id { get; init; }
    
        public static Class1 DeepClone(Class1 clone) => new Class1()
        {
            Id = clone.Id,
            Class2 = Class2.DeepClone(clone.Class2)
        };
    }
    
    public class Class2
    {
        public Class3 Class3 { get; init; }
        public string Id { get; init; }
    
        public static Class2 DeepClone(Class2 clone) => new Class2()
        {
            Id = clone.Id,
            Class3 = Class3.DeepClone(clone.Class3)
        };
    }
    
    public class Class3
    {
        public string Id { get; init; }
        public static Class3 DeepClone(Class3 clone)
            => new Class3() { Id = clone.Id };
    }
    

    Then you'd write close like this:

    Class1 object1 = new Class1()
    {
        Id = "C1",
        Class2 = new Class2()
        {
            Id = "C2",
            Class3 = new Class3()
            {
                Id = "C3",
            }
        }
    };
    
    Class1 object1c = Class1.DeepClone(object1);
    

    In either case, you're 100% in control of the deep cloning process and it's explicit what you're doing.