Shallow Copying vs Deep Copying in C#

In this article, I will show examples of shallow copying and deep copying in C#, we can examine the differences, and I will give you a few pointers.

Shallow Copying and Deep Copying

Shallow copying isn’t a new concept to most programmers. We do it all the time. Reference types (objects) are just a reference to a value on the heap. If this is not familiar to you, read this article. Here is a pretty simple scenario to show this functionality.

Figure 1.1 – Shallow Copy Example

Contact c1 = new Contact
{
    LastName = "Galt",
    FirstName = "John"
};

Contact c2 = c1;

c2.FirstName = "Who is John";

Console.WriteLine($"c1.FirstName: {c1.FirstName}");
Console.WriteLine($"c2.FirstName: {c2.FirstName}");

This is pretty straightforward. I am creating a contact object and then copying the reference to that object to another variable. Changing one of them changes both of them.

Figure 1.2 – Output

c1.FirstName: Who is John
c2.FirstName: Who is John

There are several methods available for creating deep copies but I prefer MemberwiseClone.

First, we need to change the model object to inherit the ICloneable interface and implement that interface simply.

Figure 1.3 – ICloneable Implementation

public class Contact : ICloneable
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string EmailAddress { get; set; }
    public Guid ID { get; set; }
    public string PhoneNumber { get; set; }
    public Company Company { get; set; }

    public object Clone()
    {
        return this.MemberwiseClone();
    }
}

This will give us access to the Clone method and open up a path for Deep Copying.

Now, when we run similar code to before, the results will be better.

Figure 1.4 – Getting a Slightly Deeper Copy with Clone

Contact c1 = new Contact
{
    LastName = "Galt",
    FirstName = "John"
};

Contact c2 = (Contact)c1.Clone();

c2.FirstName = "Who is John";

Console.WriteLine($"c1.FirstName: {c1.FirstName}");
Console.WriteLine($"c2.FirstName: {c2.FirstName}");

Figure 1.5 – Output with ICloneable

c1.FirstName: John
c2.FirstName: Who is John

This actually creates a deep copy of the Contact object but doesn’t go far enough. It is not a true deep copy.

Figure 1.6 – The Company Object

Contact c1 = new Contact
{
    LastName = "Galt",
    FirstName = "John", 
    Company = new Company
    {
        Name = "The John Galt Line"
    }
};

Contact c2 = (Contact)c1.Clone();

c2.Company.Name = "Rearden Steel";

Console.WriteLine($"c1.Company.Name: {c1.Company.Name}");
Console.WriteLine($"c2.Company.Name: {c2.Company.Name}");

This is similar to our earlier scenarios. After the Clone operation, c2 is a completely new object but c1.Company and c2.Company both still have the same reference to the same object on the heap.

Figure 1.7 – Output from Company Reference

c1.Company.Name: Rearden Steel
c2.Company.Name: Rearden Steel

So, there is an option available to us. One that is actually pretty easy to implement and will get us where we are looking to go. It is a two-step process. First, implement ICloneable on the Company object. This will enable us to create a clone of the company object. Second, we need to change the Clone method on the Contact object to take advantage of the fact that Company is now cloneable as well.

Figure 1.8 – Deep Copy

public object Clone()
{
    Contact contact = (Contact)this.MemberwiseClone();

    contact.Company = (Company)Company.Clone();

    return contact;
}

Here we are simply exerting a bit more control over the Clone method by also cloning object properties on the Contact object which creates a true deep copy.

Figure 1.9 – Deep Copy Output

c1.Company.Name: The John Galt Line
c2.Company.Name: Rearden Steel

Conclusion

As this post shows, creating a deep copy is not as simple on the surface as it seems and it actually requires a strategy to create deep copies. It requires an implementation from top to bottom throughout the models of your project and should be implemented based on need and usually not by default.

Leave a Reply

Your email address will not be published.