Back to Blog
C#

C# Shallow Copy vs Deep Copy

c# shallow copy vs deep copy: Understand the difference between shallow and deep copying in C#, how to implement each, and when to choose one over the other.

C#Object CopyingMemberwiseCloneDeep CopySerialization
Diagram of shallow copy and deep copy of an object graph in C#.

c# shallow copy vs deep copy requires a clear understanding of the core syntax, runtime behavior, and practical implementation patterns demonstrated in the examples below.

When you copy an object in C#, the result depends on whether you perform a shallow copy or a deep copy. The difference matters when an object contains reference types. A shallow copy duplicates the top-level object but shares references to nested objects. A deep copy duplicates everything, creating independent copies of all referenced objects. Understanding this distinction is essential for avoiding subtle bugs in your code.

What Shallow Copy Means in C#

A shallow copy creates a new object that has the same field values as the original. For value-type fields, the values are copied directly. For reference-type fields, only the references are copied, so both the original and the copy point to the same underlying objects. In practice, this means that modifying a nested object through the copy also changes it in the original.

Consider a simple class:

public class Address { public string Street { get; set; } public string City { get; set; } } public class Person { public string Name { get; set; } public int Age { get; set; } public Address Home { get; set; } }

If you copy a Person instance and only duplicate its fields, the Home reference remains shared. Any change to Home.Street via the copy is visible in the original. This is often the default behavior when you use MemberwiseClone.

What Deep Copy Means in C#

A deep copy creates a new object where every reference-type field is also copied recursively. The result is a fully independent object graph. Changing a nested object in the copy does not affect the original. Deep copying is more expensive because it requires allocating new instances for all referenced objects, and it must handle cycles and shared references carefully.

Using the same Person class, a deep copy would create a new Address object for the copy. The Name string is immutable, so it can be shared safely, but mutable reference types like Address need to be duplicated.

Implementing Shallow Copy with MemberwiseClone

The simplest way to create a shallow copy is to use Object.MemberwiseClone. This protected method creates a new object and copies all fields by value. It works for any class without requiring you to write copying logic.

public class Person { public string Name { get; set; } public int Age { get; set; } public Address Home { get; set; } public Person ShallowCopy() { return (Person)this.MemberwiseClone(); } }

MemberwiseClone is fast because it performs a direct memory copy of the object's fields. However, it does not call any constructors, so you cannot rely on initialization logic that would normally run. It also does not handle reference types specially, which is why the copy shares nested objects.

Implementing Deep Copy Manually

To create a deep copy, you need to explicitly copy every reference-type field. For simple object graphs, you can write a method that creates new instances of each nested object.

public class Person { public string Name { get; set; } public int Age { get; set; } public Address Home { get; set; } public Person DeepCopy() { return new Person { Name = this.Name, Age = this.Age, Home = new Address { Street = this.Home.Street, City = this.Home.City } }; } }

This approach gives you full control and is easy to read. However, it becomes tedious when the object graph is large or changes frequently. Every time you add a field, you must update the copy method. It also fails silently if you forget to copy a nested object.

Using Serialization for Deep Copy

A common alternative is to serialize the object to a stream and then deserialize it back. This creates a deep copy without writing manual copying logic. The .NET System.Text.Json serializer can be used for this purpose, as can BinaryFormatter (though it is deprecated for security reasons).

using System.Text.Json; public static T DeepCopy<T>(T source) { string json = JsonSerializer.Serialize(source); return JsonSerializer.Deserialize<T>(json); }

Serialization works well for objects that are serializable and do not contain references that should be shared, such as singletons or cached services. It also handles complex graphs automatically. The downside is performance: serialization is much slower than manual copying and can allocate a significant amount of memory. It also requires all types in the graph to be serializable.

Performance and Memory Considerations

Shallow copying is generally cheap because it only copies the top-level object's fields. Deep copying is more expensive because it allocates new objects for every reference in the graph. The cost grows with the size and depth of the object graph. If you need to copy objects frequently in a hot path, a manual deep copy method is usually faster than serialization.

Another consideration is memory. A shallow copy shares nested objects, so the total memory footprint remains low. A deep copy duplicates all nested objects, increasing memory usage. If the original object graph is large and you only need a few independent copies, the extra memory may be acceptable. If you copy objects repeatedly, the allocation overhead can become a problem.

Serialization also has a hidden cost: it converts the object to a text or binary representation, which requires CPU time and temporary buffers. For most application scenarios, this overhead is negligible, but for high-throughput services it can be significant.

Choosing Between Shallow and Deep Copy

Use a shallow copy when you need a new top-level object but are fine with sharing nested state. This is common for immutable objects where the nested references are also immutable, or when you only need to replace the root object's value-type fields.

Use a deep copy when you need to modify nested objects without affecting the original. This is typical for data transfer objects, domain entities, or configuration objects that will be mutated independently. If the object graph is simple and stable, a manual deep copy is clear and efficient. If the graph is complex or changes often, serialization reduces maintenance but adds runtime cost.

One important edge case is cyclic references. Manual deep copy must handle cycles to avoid infinite recursion. Serialization frameworks often handle cycles automatically, but they may require configuration. For example, System.Text.Json does not support cycles by default, so you would need to use a reference-preserving serializer or implement cycle detection yourself.

Another consideration is inheritance. MemberwiseClone works with derived types because it copies the runtime type. A manual deep copy method that returns a base type may lose derived-specific fields. Serialization preserves the concrete type if you use the correct settings, but it can be tricky with polymorphism.

In practice, the choice comes down to how much independence you need, how complex the object graph is, and how often the copying occurs. For a one-off copy in a low-traffic path, serialization is convenient. For performance-sensitive code, a hand-written deep copy is usually better.

c# shallow copy vs deep copy: Practical Usage and Code Examp | RYUSLOG DEV