C# – Copy a dictionary

The simplest way to make a copy of a dictionary is to use the copy constructor. You pass the dictionary you want to copy to the new dictionary’s constructor, like this:

using System.Collections.Generic;

var dictionary = new Dictionary<int, string>()
{
    [100] = "Bob",
    [101] = "Linda",
    [102] = "Teddy"
};

var copyOfDictionary = new Dictionary<int, string>(dictionary);

foreach(var kvp in copyOfDictionary)
{
    Console.WriteLine($"{kvp.Key}={kvp.Value}");
}
Code language: C# (cs)

Note: If you’re using a non-default comparer, you’ll want to pass it to the copy constructor (i.e. new Dictionary(d, d.Comparer)).

The copy constructor handles copying the source dictionary’s key/value pairs to the new dictionary. This example outputs the new dictionary’s contents (notice it’s the same content as the source dictionary up above):

100=Bob
101=Linda
102=TeddyCode language: plaintext (plaintext)

The copy constructor does a shallow copy. It copies values and references from the source dictionary to the new dictionary. This is fine for value types (i.e. int, bool, etc…) and strings. But it can be a problem for mutable reference types (aka objects with properties you can change) because the two dictionaries point to the exact same objects. When that’s a problem for you, you can do a deep copy. I’ll show how to do that next.

Note: Read about copying objects for more details about shallow vs deep copy.

Deep copy with a serializer

The simplest way to create a deep copy of a dictionary is by serializing it and then deserializing to a new dictionary. I’ll show two options: one using a JSON serializer and the other using a binary serializer (about 4x faster than JSON).

Option 1 – Deep copy with a JSON serializer (simple, but slow)

You can use the built-in JsonSerializer (in System.Text.Json) to serialize the dictionary to JSON, and then deserialize it to a new dictionary. This effectively creates a deep copy. Here’s an example:

using System.Collections.Generic;
using System.Text.Json;

//Initialize dictionary 
var dictionary = new Dictionary<string, Person>()
{
    ["abc"] = new Person() { Name = "Bob", Age = 42, Pet = new Pet() { Name = "Whiskers" } },
    ["def"] = new Person() { Name = "Linda", Age = 40, Pet = new Pet() { Name = "Spot" } },
    ["ghi"] = new Person() { Name = "Teddy", Age = 45, Pet = new Pet() { Name = "Scamp" } },
};

//Make a deep copy
var json = JsonSerializer.Serialize(dictionary);
var copyOfDictionary = JsonSerializer.Deserialize<Dictionary<string, Person>>(json);

//Show that it's a deep copy
foreach (var kvp in copyOfDictionary)
{
    var copyPerson = kvp.Value;
    var originalPerson = dictionary[kvp.Key];

    Console.WriteLine($"{kvp.Key}={copyPerson.Name}.");
    Console.WriteLine($"\tSame Person object? {ReferenceEquals(copyPerson, originalPerson)}");
    Console.WriteLine($"\tSame Pet object? {ReferenceEquals(copyPerson.Pet, originalPerson.Pet)}");
}
Code language: C# (cs)

This outputs the following, showing that it did a deep copy:

abc=Bob.
        Same Person object? False
        Same Pet object? False
def=Linda.
        Same Person object? False
        Same Pet object? False
ghi=Teddy.
        Same Person object? False
        Same Pet object? FalseCode language: plaintext (plaintext)

This is the simplest option because you can use the built-in JsonSerializer class and don’t have to do much coding effort. But it’s relatively slow (~4x slower than the binary serializer).

Option 2 – Deep copy with a binary serializer (faster)

When you want faster deep copying, you can use a binary serializer, such as GroBuf. This is ~4x faster than the JSON serializer.

First, install the GroBuf package (note: Package Manager Console syntax in VS).

Install-Package GroBuf
Code language: PowerShell (powershell)

Now use the GroBuf serializer to create a deep copy of the dictionary:

using GroBuf.DataMembersExtracters;
using GroBuf;
using System.Collections.Generic;

//Initialize dictionary
var dictionary = new Dictionary<string, Person>()
{
    ["abc"] = new Person() { Name = "Bob", Age = 42, Pet = new Pet() { Name = "Whiskers" } },
    ["def"] = new Person() { Name = "Linda", Age = 40, Pet = new Pet() { Name = "Spot" } },
    ["ghi"] = new Person() { Name = "Teddy", Age = 45, Pet = new Pet() { Name = "Scamp" } },
};

//Make the deep copy
var groBufSerializer = new Serializer(new PropertiesExtractor(), options: GroBufOptions.WriteEmptyObjects);
var copyOfDictionary = groBufSerializer.Copy(dictionary);


//Show that it's a deep copy
foreach (var kvp in copyOfDictionary)
{
    var copyPerson = kvp.Value;
    var originalPerson = dictionary[kvp.Key];

    Console.WriteLine($"{kvp.Key}={copyPerson.Name}.");
    Console.WriteLine($"\tSame Person object? {ReferenceEquals(copyPerson, originalPerson)}");
    Console.WriteLine($"\tSame Pet object? {ReferenceEquals(copyPerson.Pet, originalPerson.Pet)}");
}
Code language: C# (cs)

Note: You can also use Serialize() and Deserialize(), but Copy() is a nice shortcut for that (it’s also a little bit faster than those using those two methods). Props to the dev who created this well-designed library!

This example outputs the following, showing that it deep copied the dictionary:

abc=Bob.
        Same Person object? False
        Same Pet object? False
def=Linda.
        Same Person object? False
        Same Pet object? False
ghi=Teddy.
        Same Person object? False
        Same Pet object? FalseCode language: plaintext (plaintext)

This is way faster than the JSON approach, but still ~2x slower than the fastest approach, which I’ll show next.

Deep copy manually (faster than serializer)

This approach deep copies a dictionary ~2x faster than a binary serializer (and ~8x faster than JSON). It requires more effort though, so you’ll have to decide if the performance gains are worth it.

First, implement DeepCopy() in the class you’re copying:

  • Use MemberwiseClone() to create a shallow copy of the object.
  • Deep copy each mutable reference type property (i.e. object with properties you can change) by implementing DeepCopy().
    • Note: This is a recursive definition because you have to implement DeepCopy() in every class involved (hence why this is hard work!).

Here’s an example of implementing DeepCopy() in the Person class and Pet class:

public class Person
{
    public string Name { get; set; }
    public int Age { get; set; }

    public Pet Pet { get; set; }

    public Person DeepCopy()
    {
        var person = (Person)MemberwiseClone();
        person.Pet = Pet.DeepCopy();
        return person;
    }
}

public class Pet
{
    public string Name { get; set; }

    public Pet DeepCopy()
    {
        var pet = (Pet)MemberwiseClone();
        return pet;
    }
}
Code language: C# (cs)

Note: You don’t need to deep copy strings. Even though they’re reference types, they’re immutable (can’t change them). So it doesn’t hurt for multiple things to reference the same string.

Now use ToDictionary() to generate a new dictionary from the source dictionary. In the elementSelector lambda, call DeepCopy() on each value. Like this:

using System.Collections.Generic;
using System.Linq;

//Initialize dictionary
var dictionary = new Dictionary<string, Person>()
{
    ["abc"] = new Person() { Name = "Bob", Age = 42, Pet = new Pet() { Name = "Whiskers" } },
    ["def"] = new Person() { Name = "Linda", Age = 40, Pet = new Pet() { Name = "Spot" } },
    ["ghi"] = new Person() { Name = "Teddy", Age = 45, Pet = new Pet() { Name = "Scamp" } },
};

//Make the deep copy
var copyOfDictionary = dictionary.ToDictionary(kvp => kvp.Key, kvp => kvp.Value.DeepCopy());

//Show that it's a deep copy
foreach (var kvp in copyOfDictionary)
{
    var copyPerson = kvp.Value;
    var originalPerson = dictionary[kvp.Key];

    Console.WriteLine($"{kvp.Key}={copyPerson.Name}.");
    Console.WriteLine($"\tSame Person object? {ReferenceEquals(copyPerson, originalPerson)}");
    Console.WriteLine($"\tSame Pet object? {ReferenceEquals(copyPerson.Pet, originalPerson.Pet)}");
}
Code language: C# (cs)

This outputs the following, showing that it deep copied the dictionary:

abc=Bob.
        Same Person object? False
        Same Pet object? False
def=Linda.
        Same Person object? False
        Same Pet object? False
ghi=Teddy.
        Same Person object? False
        Same Pet object? FalseCode language: plaintext (plaintext)

Leave a Comment