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.