How to Clone Collections in C#: Deep vs Shallow
Cloning collections in C# involves creating a duplicate of a list, array, or dictionary, but the resulting behavior depends entirely on whether you perform a shallow copy or a deep copy. A shallow clone copies only the collection container, meaning any reference-type elements inside still point to the original objects in memory. In contrast, a deep clone recursively duplicates both the collection structure and all the distinct objects contained within it. Understanding how to implement each technique is essential for managing object state, preventing unintended side effects, and maintaining memory integrity across your application.
Understanding Shallow Cloning
A shallow clone creates a new collection instance, but copies only
the references of the elements if the elements are reference types. For
value types (like int, double,
struct), shallow cloning behaves effectively like a deep
clone because the values themselves are duplicated.
If you modify the cloned collection by adding or removing items, the original collection remains unaffected. However, if you modify the internal properties of a reference-type item inside the cloned collection, that change reflects in the original collection because both collections point to the same memory addresses.
Methods for Shallow Cloning
C# provides several built-in, highly optimized ways to create shallow copies:
var original = new List<Person>
{
new Person { Name = "Alice" },
new Person { Name = "Bob" }
};
// 1. Using the copy constructor
var shallow1 = new List<Person>(original);
// 2. Using LINQ's ToList()
var shallow2 = original.ToList();
// 3. Using the collection expression (C# 12+)
List<Person> shallow3 = [.. original];
// 4. Using Array.Copy (for arrays)
Person[] originalArray = original.ToArray();
Person[] shallowArray = new Person[originalArray.Length];
Array.Copy(originalArray, shallowArray, originalArray.Length);The Reference Side Effect
// Modifying a property inside a shallow clone:
shallow1[0].Name = "Charlie";
// Result:
// original[0].Name is now also "Charlie"Understanding Deep Cloning
A deep clone creates a new collection instance and recursively instantiates brand-new copies of every element within it. Modifying an object inside a deep-cloned collection has zero impact on the corresponding object in the original collection.
Because .NET does not provide a universal, built-in deep copy mechanism for collections, developers typically rely on one of two approaches: manual cloning or serialization.
Method 1: Manual Copying (Best Performance)
The most performant and type-safe approach is manually copying elements, either via a copy constructor, a factory method, or a dedicated clone method.
public class Person
{
public string Name { get; set; }
// Copy constructor
public Person(Person other)
{
Name = other.Name;
}
public Person() { }
}
// Deep clone using LINQ and the copy constructor
var deepClone = original.Select(p => new Person(p)).ToList();
// Modifying the cloned object does not affect the original:
deepClone[0].Name = "Charlie";
// original[0].Name remains "Alice"If your objects contain nested reference types, the copy constructor must also instantiate new copies of those child objects.
Method 2: JSON Serialization (Best Convenience)
For complex object graphs with multiple levels of nested references, manual copying can become tedious and error-prone. Serializing the collection to JSON and deserializing it back into a new collection creates a completely independent copy.
Using System.Text.Json:
using System.Text.Json;
public static class CollectionExtensions
{
public static List<T> DeepClone<T>(this List<T> source)
{
if (source == null) return null;
string json = JsonSerializer.Serialize(source);
return JsonSerializer.Deserialize<List<T>>(json);
}
}
// Usage:
var deepClone = original.DeepClone();Considerations when using serialization:
- Elements must have parameterless constructors or supported deserialization constructors.
- Private fields and properties without getters/setters may not be copied by default.
- It is significantly slower than manual cloning and consumes more memory, making it less suitable for high-frequency or performance-critical loops.
Summary of Differences
| Feature | Shallow Clone | Deep Clone |
|---|---|---|
| New Collection Instance | Yes | Yes |
| New Element Instances | No (References shared) | Yes (Independent copies) |
| Performance | Extremely fast | Slower (requires object allocations) |
| Memory Footprint | Minimal | Proportional to the size of all elements |
| Best Used For | Value types, immutable objects
(string, record), or read-only
collections |
Mutable complex objects that will be altered independently |