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.
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.
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.