How create a new deep copy (clone) of a List<T>?
C#ListCloneDeep CopyC# Problem Overview
In the following piece of code,
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Windows.Forms;
namespace clone_test_01
{
public partial class MainForm : Form
{
public class Book
{
public string title = "";
public Book(string title)
{
this.title = title;
}
}
public MainForm()
{
InitializeComponent();
List<Book> books_1 = new List<Book>();
books_1.Add( new Book("One") );
books_1.Add( new Book("Two") );
books_1.Add( new Book("Three") );
books_1.Add( new Book("Four") );
List<Book> books_2 = new List<Book>(books_1);
books_2[0].title = "Five";
books_2[1].title = "Six";
textBox1.Text = books_1[0].title;
textBox2.Text = books_1[1].title;
}
}
}
I use a Book
object type to create a List<T>
and I populate it with a few items giving them a unique title (from 'one' to 'five').
Then I create List<Book> books_2 = new List<Book>(books_1)
.
From this point, I know it's a clone of the list object, BUT the book objects from book_2
are still a reference from the book objects in books_1
. It's proven by making changes on the two first elements of books_2
, and then checking those same elements of book_1
in a TextBox
.
books_1[0].title and books_2[1].title
have indeed been changed to the new values of books_2[0].title and books_2[1].title
.
NOW THE QUESTION
How do we create a new hard copy of a List<T>
? The idea is that books_1
and books_2
become completely independent of each other.
I'm disappointed Microsoft didn't offer a neat, fast and easy solution like Ruby are doing with the clone()
method.
What would be really awesome from helpers is to use my code and alter it with a workable solution so it can be compiled and work. I think it will truly help newbies trying to understand offered solutions for this issue.
EDIT: Note that the Book
class could be more complex and have more properties. I tried to keep things simple.
C# Solutions
Solution 1 - C#
You need to create new Book
objects then put those in a new List
:
List<Book> books_2 = books_1.Select(book => new Book(book.title)).ToList();
Update: Slightly simpler... List<T>
has a method called ConvertAll
that returns a new list:
List<Book> books_2 = books_1.ConvertAll(book => new Book(book.title));
Solution 2 - C#
Create a generic ICloneable<T>
interface which you implement in your Book
class so that the class knows how to create a copy of itself.
public interface ICloneable<T>
{
T Clone();
}
public class Book : ICloneable<Book>
{
public Book Clone()
{
return new Book { /* set properties */ };
}
}
You can then use either the linq or ConvertAll
methods that Mark mentioned.
List<Book> books_2 = books_1.Select(book => book.Clone()).ToList();
or
List<Book> books_2 = books_1.ConvertAll(book => book.Clone());
Solution 3 - C#
> I'm disappointed Microsoft didn't offer a neat, fast and easy solution like Ruby are doing with the clone()
method.
Except that does not create a deep copy, it creates a shallow copy.
With deep copying, you have to be always careful, what exactly do you want to copy. Some examples of possible issues are:
- Cycle in the object graph. For example,
Book
has anAuthor
andAuthor
has a list of hisBook
s. - Reference to some external object. For example, an object could contain open
Stream
that writes to a file. - Events. If an object contains an event, pretty much anyone could be subscribed to it. This can get especially problematic if the subscriber is something like a GUI
Window
.
Now, there are basically two ways how to clone something:
- Implement a
Clone()
method in each class that you need cloned. (There is alsoICloneable
interface, but you should not use that; using a customICloneable<T>
interface as Trevor suggested is okay.) If you know that all you need is to create a shallow copy of each field of this class, you could useMemberwiseClone()
to implement it. As an alternative, you could create a “copy constructor”:public Book(Book original)
. - Use serialization to serialize your objects into a
MemoryStream
and then deserialize them back. This requires you to mark each class as[Serializable]
and it can also be configured what exactly (and how) should be serialized. But this is more of a “quick and dirty” solution, and will most likely also be less performant.
Solution 4 - C#
Well,
If you mark all involved classes as serializable you can :
public static List<T> CloneList<T>(List<T> oldList)
{
BinaryFormatter formatter = new BinaryFormatter();
MemoryStream stream = new MemoryStream();
formatter.Serialize(stream, oldList);
stream.Position = 0;
return (List<T>)formatter.Deserialize(stream);
}
Source:
Solution 5 - C#
You can use this:
var newList= JsonConvert.DeserializeObject<List<Book>>(list.toJson());
Solution 6 - C#
List<Book> books_2 = new List<Book>(books_2.ToArray());
That should do exactly what you want. Demonstrated here.
Solution 7 - C#
C# 9 records and with expressions can make it a little easier, especially if your type has many properties.
You can use something like:
var books2 = books1.Select(b => b with { }).ToList();
I did this as an example:
record Book
{
public string Name { get; set; }
}
static void Main()
{
List<Book> books1 = new List<Book>()
{
new Book { Name = "Book1.1" },
new Book { Name = "Book1.2" },
new Book { Name = "Book1.3" }
};
var books2 = books1.Select(b => b with { }).ToList();
books2[0].Name = "Changed";
books2[1].Name = "Changed";
Console.WriteLine("Books1 contains:");
foreach (var item in books1)
{
Console.WriteLine(item);
}
Console.WriteLine("Books2 contains:");
foreach (var item in books2)
{
Console.WriteLine(item);
}
}
And the output was: (Changes made to objects in Books2 did not affect original objects in Books1) > Books1 contains: > > Book { Name = Book1.1 } > > Book { Name = Book1.2 } > > Book { Name = Book1.3 } > > Books2 contains: > > Book { Name = Changed } > > Book { Name = Changed } > > Book { Name = Book1.3 }
Solution 8 - C#
Since Clone
would return an object instance of Book, that object would first need to be cast to a Book before you can call ToList
on it. The example above needs to be written as:
List<Book> books_2 = books_1.Select(book => (Book)book.Clone()).ToList();
Solution 9 - C#
public static class Cloner
{
public static T Clone<T>(this T item)
{
FieldInfo[] fis = item.GetType().GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
object tempMyClass = Activator.CreateInstance(item.GetType());
foreach (FieldInfo fi in fis)
{
if (fi.FieldType.Namespace != item.GetType().Namespace)
fi.SetValue(tempMyClass, fi.GetValue(item));
else
{
object obj = fi.GetValue(item);
if (obj != null)
fi.SetValue(tempMyClass, obj.Clone());
}
}
return (T)tempMyClass;
}
}
Solution 10 - C#
If the Array class meets your needs, you could also use the List
Reference: http://msdn.microsoft.com/en-us/library/x303t819(v=vs.110).aspx
Solution 11 - C#
Straight forward simple way to copy any generic list :
List<whatever> originalCopy=new List<whatever>();//create new list
originalCopy.AddRange(original);//perform copy of original list