In this article, we will look at all the different ways we can clone a List in C# to another and what we should pay attention to when doing so.

To download the source code for this article, you can visit our GitHub repository.

In C# we have ample ways of doing that, so let’s dive right in.

How to Clone a List in C# That Contains Value Types

A List<T>, which contains value types, is easy to clone as value types directly contain an instance of their type. 

Support Code Maze on Patreon to get rid of ads and get the best discounts on our products!
Become a patron at Patreon!

As we all love pizza, we are going to be cloning a list of toppings with all the methods in this section. Now, we are going to use a string in the example even though it is not a value type but a reference type. On the other hand, it is immutable and behaves as a value type, so we can use it in this section:

var toppings = new List<string>
{
    "Mozzarella",
    "Olive oil",
    "Basil"
};

Here, we define a toppings variable of List<string> type, that holds the toppings for the classic Margherita pizza.

Next, we’ll look at the options for cloning the values of that List<string> to another.

Using the List’s Constructor

One of the overloads of the List<T>‘s constructor takes in an IEnumerable<T>:

var toppingsClonedWithConstructor = new List<string>(toppings);

Here, we initialize a new toppingsClonedWithConstructor variable that contains the copied elements from our toppings list.

Using the List’s CopyTo Method

List<T> has several methods that we can use to clone its contents to another list or collection.

One of those is the CopyTo method. The list inherits it from the ICollection<T> interface and it can be used to clone the List<T> to a T[]:

var toppingsClonedWithCopyTo = new string[toppings.Count];
toppings.CopyTo(toppingsClonedWithCopyTo);

First, we initialize a toppingsClonedWithCopyTo variable as a string[] that has a length equal to the length of the toppings list – hence the toppings.Count. Then we use the CopyTo method on the toppings list and pass the newly initialized array as a parameter. This way its contents are copied over to toppingsClonedWithCopyTo.

Using the List’s AddRange Method

Another method that takes in an IEnumerable<T> as a parameter is the AddRange method:

var toppingsClonedWithAddRange = new List<string>();
toppingsClonedWithAddRange.AddRange(toppings);

The AddRange method needs a List<T> that is already initialized, so we declare the toppingsClonedWithAddRange variable and assign an empty List<string> to it. Then we pass the toppings as a parameter to the AddRange method and it clones the contents for us.

Using the Enumerable’s ToList Method

The System.Linq namespace provides us with the Enumerable.ToList method:

var toppingsClonedWithToList = toppings.ToList();

Here, we directly initialize our toppingsClonedWithToList variable by calling the ToList method on our already existing toppings variable, which clones its contents to our new list.

Alternatively, we can use the Enumerable.ToArray method in the same way if we want to clone the contents of a List<T> to T[].

Using the ConvertAll Method

List<T> has another useful method that might look a bit daunting on the surface – this is the ConvertAll<TOutput>(Converter<T, TOutput>) method. It converts all the members of a list from one type to another and returns a list containing the converted members. We can even use it for cloning:

var toppingsClonedWithConvertAll = toppings
    .ConvertAll(new Converter<string, string>(x => x));

We start by initializing a toppingsClonedWithConvertAll variable and assigning it the value returned from using the ConvertAll method on our toppings list. The method takes in a Converter<TInput, TOutput>, which is just a delegate for a method that converts an element from one type to another. It takes in the name of a method used for the conversion, or we can pass an anonymous method as well.

We don’t need a separate method to convert the element we pass in, so we simply use a lambda expression that returns the same value, hence the x => x.

Using the ICloneable Interface

A bit more complex way of cloning a List<T> can be achieved using the ICloneable interface. But before we can use the interface’s Clone method, we need to set things up:

public class ToppingsList<T> : List<T>, ICloneable
{
    public object Clone()
    {
        return this.MemberwiseClone();
    }
}

We create a generic ToppingsList<T> class in a new file that inherits List<T> and ICloneable. We also implement the Clone method by just returning the result of the Object‘s (the ultimate base class in .NET) MemberwiseClone method which gives us a shallow copy of the object.

Once we have done that, we go back to our Program class:

var customToppingsList = new ToppingsList<string>
{
    "Mozzarella",
    "Olive oil",
    "Basil"                
};

With this, we create a customToppingsList variable of our new generic type as ToppingsList<string> and initialize it like a regular List<string>. 

Then we continue with:

var toppingsClonedWithICloneable = (ToppingsList<string>)customToppingsList.Clone();

We declare a toppingsClonedWithICloneable variable and use the Clone method on our customToppingsList. We also cast the result to ToppingsList<string> as the method itself returns an object.

To make sure that everything we have done to clone a List containing value types in C# works as intended, we can print all the results on the console:

Console.WriteLine("Original list: " + string.Join(", ", toppings));
Console.WriteLine("Cloned with Constructor: " + string.Join(", ", toppingsClonedWithConstructor));
Console.WriteLine("Cloned with CopyTo: " + string.Join(", ", toppingsClonedWithCopyTo));
Console.WriteLine("Cloned with AddRange: " + string.Join(", ", toppingsClonedWithAddRange));
Console.WriteLine("Cloned with ToList: " + string.Join(", ", toppingsClonedWithToList));
Console.WriteLine("Cloned with ConverAll: " + string.Join(", ", toppingsClonedWithConvertAll));
Console.WriteLine("Cloned with ICloneable: " + string.Join(", ", toppingsClonedWithICloneable));

And check the result:

Original list: Mozzarella, Olive oil, Basil
Cloned with Constructor: Mozzarella, Olive oil, Basil
Cloned with CopyTo: Mozzarella, Olive oil, Basil
Cloned with AddRange: Mozzarella, Olive oil, Basil
Cloned with ToList: Mozzarella, Olive oil, Basil
Cloned with ConverAll: Mozzarella, Olive oil, Basil
Cloned with ICloneable: Mozzarella, Olive oil, Basil

How to Clone a List in C# That Contains Reference Types

Reference type variables, unlike value type ones, store a reference to the data and not the data itself. This means that when we work with reference types, two variables can reference the same data – the downside here is that operations on one variable can affect the data referenced by another. 

Building on the pizza trend from earlier, let’s expand on our example:

public class Pizza
{
    public string Name { get; set; }
    public List<string> Toppings { get; set; }

    public override string ToString()
    {
        return $"Pizza name: {Name}; Toppings: {string.Join(", ", Toppings)}";
    }
}

In a separate file, we declare a Pizza class with two simple properties – Name and Toppings. We also override the ToString method for easy visualization. 

We are cloning lists, so we need one with reference types:

var pizzas = new List<Pizza>
{
    new Pizza
    {
        Name= "Margherita",
        Toppings = new List<string>
        {
            "Mozzarella",
            "Olive oil",
            "Basil"
        }
    },
    new Pizza
    {
        Name= "Diavola",
        Toppings = new List<string>
        {
            "Mozzarella",
            "Ventricina",
            "Chili peppers"
        }
    }
};

Here we declare a pizzas variable of type List<Pizza> and add two pizzas to it.

Now, let’s look into the two types of copying we have when dealing with reference types.

Shallow Copy

We can easily achieve a shallow copy of our pizzas list with any of the methods from the previous section. But a shallow copy of a List<T>, where T is a reference type, copies only the structure of the collection and references to its elements, not the elements themselves. This means that changes to the elements in any of the two lists will be reflected in both the original and copied lists.

Let’s illustrate this:

var clonedPizzas = pizzas.ToList();

var margherita = pizzas
    .FirstOrDefault(x => x.Name == "Margherita");

margherita.Toppings.Clear();

First, we create a clonedPizzas variable and clone the contents of pizzas to it using the ToList method (using any of the other methods used earlier will produce the same result). Then we get the Margherita pizza from the original list using the FirstOrDefault method. Finally, we use the Clear method to empty the list of Toppings for that pizza.

Now, let’s see what happens:

Console.WriteLine($"Original Margherita: {pizzas.First()}");
Console.WriteLine($"Cloned with ToList: {clonedPizzas.First()}");

We print the first pizza of each list to the console, which in both cases is the Margherita, using the First method.

We overrode the ToString method which will give us an easy-to-read representation of each pizza. Now, we can check the result:

Original Margherita: Pizza name: Margherita; Toppings:
Cloned with ToList: Pizza name: Margherita; Toppings:

We can see that both the original and copied Margherita pizzas now have an empty list of Toppings. This is because when creating a shallow copy, we only clone the references to the objects, not the actual objects. This is not ideal because when we change elements in one list, we change the elements everywhere we have copied that list.

This might cause serious problems for us, so let’s what we can do to prevent it.

Deep Copy 

The alternative is to create a deep copy – this means that we don’t just copy the references to the objects but create new, copied objects. This produces a different result than shallow copies as the objects referenced by the copied list are separate from those referenced in the original list.

Clone a List Using the ICloneable Interface

We can also use the Clone method we get from inheriting the ICloneable interface to create a deep copy.

Let’s do the necessary updates:

public class Pizza : ICloneable
{
    public string Name { get; set; }
    public List<string> Toppings { get; set; }

    public object Clone()
    {
        return new Pizza
        {
            Name = Name,
            Toppings = Toppings.ToList(),
        };
    }

    public override string ToString()
    {
        return $"Pizza name: {Name}; Toppings: {string.Join(", ", Toppings)}";
    }
}

We expand our Pizza class by inheriting ICloneable and implementing the Clone method. This time we create our implementation where we return a new Pizza object and copy the parameters of the current instance – we assign Name directly as it is a string and then we use the ToList method we learned earlier to get a clone of the Toppings list.

Then we get on with the cloning:

var pizzasClonedWithICloneable = new List<Pizza>();
            
foreach(var pizza in pizzas)
{
    pizzasClonedWithICloneable.Add((Pizza)pizza.Clone());
}

To do this we create a pizzasClonedWithICloneable variable as an empty List<Pizza>. Then, in a foreach loop iterating over our initial pizzas list, we get a new copy of the current pizza using the Clone method and add it to our new list.

Clone a List Using a Copy Constructor

A copy constructor is a constructor that takes an instance of the type as a parameter. Then the values of each property of that object are copied over to the newly created instance of that type:

public Pizza(Pizza pizza)
{
    Name = pizza.Name;
    Toppings = pizza.Toppings.ToList();
}

In our Pizza class, we create a new constructor that takes in a Pizza as a parameter. In it, we assign the Name and Toppings properties the values of the passed-in object, keeping in mind that we need to pass a copy of the Toppings, done with pizza.Toppings.ToList(), and not just assign the value.

Now, we can move back to our Program class and do the cloning:

var pizzasClonedWithCopyConstructor = new List<Pizza>();

foreach (var pizza in pizzas)
{
    pizzasClonedWithCopyConstructor.Add(new Pizza(pizza));
}

Similar to the ICloneable example, we create a new pizzasClonedWithCopyConstructor variable and initialize it with an empty List<Pizza>. Once this is done we create a foreach loop and on each iteration we use the copy constructor, new Pizza(pizza), to add a copy of the current pizza to our new list.

Now, that we have used our two methods of creating a deep copy, let’s test them:

var margherita = pizzas
    .FirstOrDefault(x => x.Name == "Margherita");

margherita.Toppings.Clear();

Console.WriteLine($"Original Margherita: {pizzas.First()}");
Console.WriteLine($"Cloned with ICloneable: {pizzasClonedWithICloneable.First()}");
Console.WriteLine($"Cloned with Copy Constructor: {pizzasClonedWithCopyConstructor.First()}");

After the last foreach loop in our Program class, we again get the Margherita pizza and clear its list of Toppings. Then we print our Margherita pizzas on the console and examine the result:

Original Margherita: Pizza name: Margherita; Toppings:
Cloned with ICloneable: Pizza name: Margherita; Toppings: Mozzarella, Olive oil, Basil
Cloned with Copy Constructor: Pizza name: Margherita; Toppings: Mozzarella, Olive oil, Basil

This time around we can see that our task to create a deep copy when trying to clone a List was successful and the cloned pizzas have their Toppings intact as we have indeed created a deep copy with both approaches.

Conclusion

In this article, we learned all about how to clone a List in C#. We also learned that shallow copies are easy to achieve in many ways and work wonders with value types but may cause headaches when used with reference types. Deep copies are very useful but tend to be more expensive than shallow ones, due to the need for additional object creation. It can also be very complicated to achieve if we deal with very complex objects. The good thing is that now you are prepared to tackle the cloning of List<T>, no matter if the T is a value or a reference type.

Liked it? Take a second to support Code Maze on Patreon and get the ad free reading experience!
Become a patron at Patreon!