Deep Copying in C#: Why Is It Every Developer's Headache?
\n\nHave you ever copied an object in C#, only to be surprised to find that changes to the "copy" break the original? Sound familiar? This is the classic problem of shallow copy. When an object has reference fields (other classes, lists, arrays), MemberwiseClone() simply copies the references. As a result, both the original and the copy point to the same nested object.
In this article, we will break down deep cloning in C# — the process of creating a fully independent copy of an object with all its nested objects. We will compare 5 popular approaches, write real code, conduct a mental benchmark, and figure out which method to choose for your project.
\n\nWhat is Deep Clone and How is it Different from Shallow Clone?
\n\nImagine you have a class Person that contains an Address:
public class Address\n{\n public string City { get; set; }\n public string Street { get; set; }\n}\n\npublic class Person\n{\n public string Name { get; set; }\n public int Age { get; set; }\n public Address HomeAddress { get; set; }\n}\n\nWith a shallow clone (the usual MemberwiseClone()), you get a new person, but their HomeAddress will point to the same Address object as the original. Change the street in the copy — the street changes in the original too. A disaster.
Deep clone solves this problem: it recursively copies all nested objects, creating a completely independent data structure.
\n\nApproach 1: Manual Implementation via ICloneable and Copy Constructors
\n\nThe most obvious, but also the most labor-intensive way is to implement the ICloneable interface or write your own DeepClone() method.
Implementation Example:
\n\npublic class Address : ICloneable\n{\n public string City { get; set; }\n public string Street { get; set; }\n\n public object Clone()\n {\n return new Address { City = this.City, Street = this.Street };\n }\n}\n\npublic class Person : ICloneable\n{\n public string Name { get; set; }\n public int Age { get; set; }\n public Address HomeAddress { get; set; }\n\n public object Clone()\n {\n return new Person\n {\n Name = this.Name,\n Age = this.Age,\n HomeAddress = (Address)this.HomeAddress?.Clone()\n };\n }\n}\n\nPros:
\n- \n
- Full control over the copying process \n
- Maximum performance \n
- Understandable code (no "magic") \n
Cons:
\n- \n
- Huge amount of boilerplate code \n
- Easy to forget to update the Clone() method when adding a new field \n
- Does not work for third-party classes (e.g., from libraries) \n
When to use: Only for simple DTOs with 2-3 fields where performance is critical (e.g., in game engines).
\n\nApproach 2: Reflection — The Universal Soldier
\n\nReflection allows you to write a universal deep cloning method that will work with any object.
\n\nCode Example:
\n\npublic static T DeepCloneByReflection<T>(T source)\n{\n if (source == null) return default;\n\n var type = source.GetType();\n \n // For value types and strings, simply return the value\n if (type.IsValueType || type == typeof(string))\n return source;\n\n // For arrays\n if (type.IsArray)\n {\n var arr = source as Array;\n var clonedArr = Array.CreateInstance(type.GetElementType(), arr.Length);\n for (int i = 0; i < arr.Length; i++)\n clonedArr.SetValue(DeepCloneByReflection(arr.GetValue(i)), i);\n