In C#, when we create a new object that is a copy of an existing object, we can either create a shallow copy or a deep copy.
Creating a deep copy of an object is often necessary when we need to modify the copy without affecting the original object, or when we need to serialize the object and its contents. In this article, we will explore the different methods for creating a deep copy of an object in C#.
However, before we dive into how to create a deep copy of an object in C#, it’s important to understand the difference between a shallow copy and a deep copy. So, let’s look at that first.
Shallow Copy vs Deep Copy
A shallow copy creates a new object that is identical to the original object in memory, but the new object still contains references to the same objects as the original object.
This means that if we modify an object that is referenced by both the original and the copied objects, the change will be visible in both objects.
Let’s understand this with a class Person
:
public class Person { public required string Name { get; set; } public required int Age { get; set; } public required Address Address { get; set; } }
The required modifier used in the property declaration was introduced in C# 11. We use it to ensure that all the members of this class are initialized upon instantiation.
The class contains a reference to Address
object:
public class Address { public required string Street { get; set; } public required string City { get; set; } public required string State { get; set; } }
To create a shallow copy, we can use the MemberwiseClone
method in the Person
class:
public Person ShallowCopy() => (Person)this.MemberwiseClone();
When we create a new Person
object and then copy it by calling this method, we are creating a shallow copy:
var originalPerson = new Person { Name = "Steve Doe", Age = 22, Address = new Address { Street = "123 Main St.", City = "Anytown", State = "AB" } }; var copiedPerson = originalPerson.ShallowCopy();
The copiedPerson
variable now points to the same object in memory as originalPerson
.
Now, let’s modify the copiedPerson
object and see its effects:
copiedPerson.Name = "Jack Swallow"; copiedPerson.Address.Street = "456 Elmo St.";
In this case, we modified the Name
property of copiedPerson
and the Street
property of its Address
object. However, when we output the Name
and Street
properties of the originalPerson
object:
Console.WriteLine($"Original Name: {originalPerson.Name}"); Console.WriteLine($"Original Street: {originalPerson.Address.Street}");
We can see that the street has changed:
Original Name: Steve Doe Original Street: 456 Elmo St.
This is a behavior of shallow copy. We can see that the reference type property has another (changed) value.
A deep copy, on the other hand, creates a new object that is identical to the original object in memory, but all the objects referenced by the original object are also copied recursively.
This ensures that changes made to the copied objects do not affect the original object.
Now that we understand the difference between a shallow copy and a deep copy, let’s explore how to create a deep copy of an object in C#.
Deep Copy With the ICloneable Interface
The easiest way to create a deep copy of an object in C# is to implement the ICloneable interface.
The ICloneable
interface defines a single method, Clone()
, which we can use to create a new object that is a copy of the original object.
However, before we continue, it’s important to note that we have to be careful with the ICloneable
interface when we want to create a deep copy of an object.
The main problem with ICloneable
interface implementation is that it provides a Clone()
method, but is vague about whether the method creates a deep copy or a shallow copy. Different classes can have their own way of implementation. Hence, we can not rely on ICloneable
to always create deep copies.
That said, let’s look at a version of the Clone()
method that creates a deep copy.
First, let’s modify our Person
class to implement ICloneable
interface:
public class Person : ICloneable { public required string Name { get; set; } public required int Age { get; set; } public required Address Address { get; set; } public object Clone() { var clonedPerson = new Person { Name = Name, Age = Age, Address = new Address() { Street = Address.Street, City = Address.City, State = Address.State } }; return clonedPerson; } }
Here, the Clone()
method creates a new instance of the Person
class and copies all the properties of the original object to the new object. It creates a new instance of the Address
class and copies the Address
property recursively by copying all its properties.
Also, we can refactor our code even further and implement the ICloneable
interface in the Address
class too:
public class Address : ICloneable { public required string Street { get; set; } public required string City { get; set; } public required string State { get; set; } public object Clone() { return new Address { Street = Street, City = City, State = State }; } }
This allows us to modify the Clone()
method in the Person
class to remove excess code:
public object Clone() { var clonedPerson = new Person { Name = Name, Age = Age, Address = (Address)Address.Clone() }; return clonedPerson; }
When we create a new Person
object using the Clone()
method, we get a deep copy of the original object, meaning that any changes made to the copy will not affect the original object:
var copiedPerson = (Person)originalPerson.Clone();
Now, if we modify the Name
and Street
properties of the copiedPerson
object:
copiedPerson.Name = "Jack Swallow"; copiedPerson.Address.Street = "456 Elmo St.";
And output the Name
and Street
properties of the originalPerson
object:
Console.WriteLine($"Original Name: {originalPerson.Name}"); Console.WriteLine($"Original Street: {originalPerson.Address.Street}");
We see that there is no change in the original object’s properties:
Original Name: Steve Doe Original Street: 123 Main St.
Copy Constructors
Another similar way to implement the same can be using a copy constructor.
It is a special constructor that creates a new object by copying the values of the fields and properties of an existing object. It provides a way to create a new object with the same values as an existing object without modifying the original object.
However, we encounter the same problem of vagueness with copy constructors. Whether the constructor creates a deep copy or a shallow copy depends on its implementation and it doesn’t inherently guarantee a deep copy.
Deep Copy With Serialization
Another way to create a deep copy of an object in C# is to use serialization.
Serialization is the process of converting an object to a byte stream that can be saved to a file or transmitted over a network. Deserialization is the process of converting the byte stream back into an object.
Before we serialize objects, it’s important to ensure that the objects we are trying to serialize are serializable and that we have marked them with necessary attributes such as, [Serializable]
:
[Serializable] public class Person { // Code removed for brevity }
There are various serialization techniques for deep copying an object. Let’s examine a few of them more closely.
XML Serialization
XML serialization is the process of converting an object into an XML format that can be stored in a file, database, or memory stream:
public static T DeepCopyXML<T>(T input) { using var stream = new MemoryStream(); var serializer = new XmlSerializer(typeof(T)); serializer.Serialize(stream, input); stream.Position = 0; return (T)serializer.Deserialize(stream); }
Here, we create a new MemoryStream
instance, which we use to store the serialized object, and an XmlSerializer
instance, which will be used to serialize and deserialize the object.
The Serialize()
method call serializes the input object. Next, we set the stream.Position
to 0 to read the MemoryStream
from the beginning. Finally, the Deserialize()
method call deserializes the object from the MemoryStream
and returns it as a new object of type T
.
Now, we can create a deep copy of the Person
object using the DeepCopyXML()
method, which uses XML serialization to create the copy.
JSON Serialization
JSON serialization is the process of converting an object into a JSON format that we can store and transport over the network:
public static T DeepCopyJSON<T>(T input) { var jsonString = JsonSerializer.Serialize(input); return JsonSerializer.Deserialize<T>(jsonString); }
Here, instead of MemoryStream
, we use the JsonSerialzer
class.
Data Contract Serialization
Data contract serialization is another serialization technique that we use to serialize and deserialize objects. It uses the [DataContract]
and [DataMember]
attributes to mark the objects that should be serialized.
Let’s modify the Person
class to enable data contract serialization:
[DataContract] public class Person { [DataMember] public required string Name { get; set; } [DataMember] public required int Age { get; set; } [DataMember] public required Address Address { get; set; } }
Data contract serialization is similar to XML serialization in that it creates an XML file to hold the serialized data. Also, it is used for data serialization in Windows Communication Foundation (WCF) messages:
public static T DeepCopyDataContract<T>(T input) { using var stream = new MemoryStream(); var serializer = new DataContractSerializer(typeof(T)); serializer.WriteObject(stream, input); stream.Position = 0; return (T)serializer.ReadObject(stream); }
The implementation is similar to serialization using XML. The difference is, here, we use a DataContractSerializer
instance to serialize and deserialize the object.
Deep Copy With Reflection
Reflection is a powerful feature in C# that allows us to inspect and manipulate objects at runtime.
Let’s understand this technique with a new method:
public static T DeepCopyReflection<T>(T input) { var type = input.GetType(); var properties = type.GetProperties(); T clonedObj = (T)Activator.CreateInstance(type); foreach (var property in properties) { if (property.CanWrite) { object value = property.GetValue(input); if (value != null && value.GetType().IsClass && !value.GetType().FullName.StartsWith("System.")) { property.SetValue(clonedObj, DeepCopyReflection(value)); } else { property.SetValue(clonedObj, value); } } } return clonedObj; }
First, we create a new instance of the same type as the original object using the CreateInstance()
method from the Activator
class.
Then, we iterate over all the properties of the input object and copy their values to the newly created object. If the property is a reference type (i.e. a class), the method recursively calls itself to create a deep copy of the object.
Thus, the DeepCopyReflection()
method takes an object of type T
and returns a deep copy of the object.
Deep Copy With Expression Trees
Expression Tree is a powerful feature in C# that allows us to dynamically create and compile code at runtime.
We can use it to generate code that performs a deep copy of an object. This technique can be useful in scenarios where we don’t have control over the classes we need to copy.
First, let’s create a method that generates an expression tree to deep copy an instance of a class:
private static Func<T, T> GenerateDeepCopy<T>() { var inputParameter = Expression.Parameter(typeof(T), "input"); var memberBindings = new List<MemberBinding>(); foreach (var propertyInfo in typeof(T).GetProperties()) { var propertyExpression = Expression.Property(inputParameter, propertyInfo); if (propertyInfo.PropertyType.IsClass && propertyInfo.PropertyType != typeof(string)) { var copyMethod = typeof(DeepCopyMaker) .GetMethod(nameof(DeepCopyMaker.DeepCopyExpressionTrees)) .MakeGenericMethod(propertyInfo.PropertyType); var propertyCopyExpression = Expression.Call(copyMethod, propertyExpression); memberBindings.Add(Expression.Bind(propertyInfo, propertyCopyExpression)); } else { memberBindings.Add(Expression.Bind(propertyInfo, propertyExpression)); } } var memberInitExpression = Expression.MemberInit(Expression.New(typeof(T)), memberBindings); return Expression.Lambda<Func<T, T>>(memberInitExpression, inputParameter).Compile(); }
The GenerateDeepCopy()
method iterates over each property of the specified type T
, and for each property that is a reference type, generates an expression to deep copy the object using recursion.
Then, we compile the resulting expression tree into a delegate that creates deep copy instances of the specified type.
Let’s create another method to execute the deep copy delegate:
public static T DeepCopyExpressionTrees<T>(T input) { return GenerateDeepCopy<T>()(input); }
The DeepCopyExpressionTrees()
method is a simple wrapper around the GenerateDeepCopy()
method that creates and executes the deep copy delegate for the specified object.
Deep Copy With Third-Party Libraries
There are several third-party libraries that provide easy-to-use and efficient methods for deep copying objects in C#. Let’s look at some of the popular libraries for creating deep copies of objects in C#.
AutoMapper
AutoMapper is an open-source library that simplifies object mapping between different types.
We get access to a Mapper.Map()
method that maps a source object to a new instance of a destination object. Also, AutoMapper can handle nested reference types, which makes it ideal for creating deep copies of objects.
We use AutoMapper by creating a mapping profile that defines how the source object should be mapped to the destination object.
Let’s see it in action.
We’ll start with creating a mapping for the Person
and Address
classes:
private IMapper _mapper; public DeepCopyMaker() { var config = new MapperConfiguration(cfg => { cfg.CreateMap<Address, Address>(); cfg.CreateMap<Person, Person>() .ForMember(dest => dest.Address, opt => opt.MapFrom(src => _mapper.Map<Address>(src.Address))); }); _mapper = config.CreateMapper(); }
The IMapper
field is an instance of the AutoMapper class that we use to perform the deep copy.
In the constructor of the DeepCopyMaker
class, we create the mappings using the CreateMap<T, T>()
method. However, there is a slight difference in how we handle the Person
class here.
Once we’ve created the Address
mapping, we must explicitly map the Address
property using the AutoMapper instance’s Map()
method. We can do this using the ForMember()
method of the mapping configuration. This tells AutoMapper to map the Address
property of the source object to the Address
property of the destination object. Thus, ensuring that the Address
object is deep-copied along with the rest of the Person
object.
Once the configuration is done, we can create a method to deep copy the Person
object:
public Person DeepCopyAutoMapper(Person input) { return _mapper.Map<Person>(input); }
Here, we use the Map()
method to create deep copies using AutoMapper.
FastDeepCloner
FastDeepCloner is a library that uses reflection to create an exact copy of an object irrespective of whether it is serializable, including nested reference types.
The default usage for this library is simpler. All we need to do is refer FastDeepCloner and create a method:
public static T DeepCopyFastDeepCloner<T>(T input) { return (T)DeepCloner.Clone(input); }
The FastDeepCloner library provides us with the DeepCloner.Clone()
method to create a deep copy of any object. The Clone()
method takes an object as input and returns a new instance of that object, with all of its properties and fields copied.
DeepCopy
DeepCopy is a simple library that uses IL code generation for deep copying objects in C#.
The library supports deep copying of complex objects, including objects with circular references, and can handle a wide variety of object types.
Let’s create a method to understand this library:
public static T DeepCopyLibraryDeepCopy<T>(T input) { return DeepCopier.Copy(input); }
With the DeepCopy library, we can create a deep copy of an object by calling the Copy()
method in the DeepCopier
class, and passing in the object that we want to copy.
We can use it similarly to deep copying with FastDeepCloner.
Json.NET
Json.NET is a popular third-party library for working with JSON in C#. Also, it provides a way to create deep copies of objects using its serialization and deserialization features.
To create a deep copy of an object using Json.NET, we can serialize the object to a JSON string using the JsonConvert.SerializeObject()
method. Then, we can deserialize the string back into a new object using the JsonConvert.DeserializeObject()
method:
public static T DeepCopyJsonDotNet<T>(T input) { var serialized = Newtonsoft.Json.JsonConvert.SerializeObject(input); return Newtonsoft.Json.JsonConvert.DeserializeObject<T>(serialized); }
This approach creates a new object with the same values as the original object, but it is not connected to the original object in any way. This works very similarly to how we previously covered the native JSON serialization in C#.
Now, let’s see how the different methods to deep copy an object compare to each other in performance.
What Is the Fastest Deep Copy Method?
Let’s create a DeepCopierBenchmark
class to compare the performance of various deep copying methods using BenchmarkDotNet:
public class DeepCopierBenchmark { private Person? _person; [GlobalSetup] public void Setup() { _person = new Person { Name = "John", Age = 45, Address = new Address { Street = "56 Jump St.", City = "Aquatica", State = "TY" } }; } }
Next, in the same class, we will implement benchmark methods for all the deep copying techniques.
The DeepCopierBenchmark
class then runs all the methods and compares their performance:
var summary = BenchmarkRunner.Run<DeepCopierBenchmark>(config); Console.WriteLine(summary);
Let’s assess the performance results:
| Method | Mean | Error | StdDev | Median | |----------------------------------- |----------------:|--------------:|--------------:|----------------:| | ICloneableBenchmark | 26.48 ns | 4.648 ns | 13.633 ns | 19.03 ns | | DeepCopyLibraryBenchmark | 36.77 ns | 0.710 ns | 0.729 ns | 36.95 ns | | ReflectionBenchmark | 575.17 ns | 8.127 ns | 7.204 ns | 575.11 ns | | JSONSerializationBenchmark | 1,318.26 ns | 22.127 ns | 22.723 ns | 1,315.41 ns | | JsonDotNetBenchmark | 2,513.89 ns | 49.568 ns | 41.392 ns | 2,524.54 ns | | FastDeepClonerBenchmark | 2,578.80 ns | 15.353 ns | 13.610 ns | 2,584.21 ns | | DataContractSerializationBenchmark | 7,788.49 ns | 155.564 ns | 381.601 ns | 7,877.53 ns | | XMLSerializationBenchmark | 13,742.08 ns | 329.596 ns | 918.782 ns | 13,764.48 ns | | ExpressionTreesBenchmark | 263,921.21 ns | 5,259.668 ns | 8,342.390 ns | 262,842.97 ns | | AutoMapperBenchmark | 1,333,546.69 ns | 18,872.505 ns | 17,653.353 ns | 1,329,250.98 ns |
As evident from the table, the ICloneable
implementation is the fastest with the next best being deep copy using the DeepCopy library.
However, it’s important to note here that ICloneable
has some limitations and is not as flexible as the other methods. It only works for classes that implement the ICloneable
interface, which is not always the case.
There are arguments against using it in public APIs due to the interface being vague about enforcing a deep copy mechanism.
In the serialization techniques, both forms of JSON serialization perform the best followed by DataContract serialization, and XML serialization respectively.
Deep copying an object using AutoMapper
results in the slowest performance.
In terms of performance, the ICloneable
interface is the way to go. However, if we already have a serialization format implemented in our application, it makes more sense to go with that. We could also take a look at one of the third-party libraries if they satisfy our requirements.
Hence, the best deep copy method to use depends on the specific needs and requirements of the application. It’s a good idea to evaluate each method in our particular use case and choose the one that provides the best balance of performance, accuracy, and ease of implementation.
Conclusion
In this article, we explored six popular methods for deep copying objects in C#: ICloneable interface, XML serialization, JSON serialization, data contract serialization, reflection, and expression trees.
Also, we looked at several third-party libraries that can provide methods to deep copy an object.
Each method has its own advantages and disadvantages. The choice of method will depend on the specific requirements and constraints of the application. When choosing a method, it is important to consider factors such as performance, serialization format, code complexity, and compatibility with the application’s architecture.