94

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.

mipe34
  • 5,596
  • 3
  • 26
  • 38
TheScholar
  • 2,527
  • 5
  • 23
  • 25

11 Answers11

137

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));
Mark Byers
  • 811,555
  • 193
  • 1,581
  • 1,452
  • 49
    What if the Book object was more complex and had thousands of other properties? – TheScholar Dec 22 '12 at 23:26
  • 22
    +1, @TheScholar - than you either create copy contructor or implement some other way to create Deep-copy of an object. – Alexei Levenkov Dec 22 '12 at 23:27
  • 15
    @TheScholar: Then it would be a badly designed class. Thousands of properties... are you serious? – Mark Byers Dec 22 '12 at 23:27
  • 12
    @MarkByers it was a figure of speeach of course. My example provide a single property for the sake of keeping the example simple and easy to read. – TheScholar Dec 22 '12 at 23:29
  • 8
    The new question can be simplified to "how do I copy a single object in C#?" – Mark Byers Dec 22 '12 at 23:34
  • 1
    Thanks a lot guys for the efforts. I realize that copying data or attempting to swap them is a bit more complicated than I expected. – TheScholar Dec 23 '12 at 02:33
  • Why is the `ToList()` not enough? Why do you need the `Select` statement too? – Robert F. Nov 11 '16 at 19:34
  • 7
    @RoberF. `ToList()` is not enough because the OP asked for a deep copy. The `ToList()` alone would be a shallow copy. The `Select` copy not only the list, but the objects inside it. – AxelWass Feb 08 '17 at 14:49
  • this is too expensive – Vidyesh Feb 01 '20 at 09:53
  • 1
    @Vidyesh - Its too expensive compared to what? The spec is to create a list *with new objects in it*. The only way to do that, is to *create new objects*. If you *don't* need "new objects", then you don't need "deep copy" - you simply do `.ToList()`. But that isn't the subject of this Q&A. – ToolmakerSteve Jul 28 '21 at 15:41
45

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());
Trevor Pilley
  • 16,156
  • 5
  • 44
  • 60
22

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:

  1. Cycle in the object graph. For example, Book has an Author and Author has a list of his Books.
  2. Reference to some external object. For example, an object could contain open Stream that writes to a file.
  3. 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:

  1. Implement a Clone() method in each class that you need cloned. (There is also ICloneable interface, but you should not use that; using a custom ICloneable<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 use MemberwiseClone() to implement it. As an alternative, you could create a “copy constructor”: public Book(Book original).
  2. 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.
svick
  • 236,525
  • 50
  • 385
  • 514
  • Why should you not use the default ICloneable interface but instead use a custom generic one? – Jan Jul 15 '21 at 08:42
  • I would also like to know why I should not use ICloneable? – d0rf47 Jun 14 '22 at 17:01
  • @d0rf47 [The documentation explains it:](https://learn.microsoft.com/en-us/dotnet/api/system.icloneable) "Because callers of Clone() cannot depend on the method performing a predictable cloning operation, we recommend that ICloneable not be implemented in public APIs." – svick Jun 15 '22 at 19:40
  • @svick I see, but lets just say for arguments sake, I could make mine perform the deep copy if i wanted to just for a small program that no one else is ever going to use right? It seems like its just not recommended since they implementation method isn't enforced and can therefor vary if returning deep vs shallow, is this a correct understanding? – d0rf47 Jun 16 '22 at 14:15
11

You can use this:

var newList= JsonConvert.DeserializeObject<List<Book>>(list.toJson());
Dominique
  • 16,450
  • 15
  • 56
  • 112
Thao Le
  • 111
  • 1
  • 2
  • This has some limitations, but worked wonderful for my use case straight out of the box. Saved me the headache of implementing copy methods everywhere. Thanks! – CitiZen Apr 01 '23 at 21:15
10

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:

https://social.msdn.microsoft.com/Forums/en-US/5c9b4c31-850d-41c4-8ea3-fae734b348c4/copy-listsomeobject-to-clone-list?forum=csharpgeneral

gatsby
  • 1,148
  • 11
  • 12
  • 1
    When everything else failed, this one helped me. I had an object type property and there was no other way which could make a copy of that property. Thanks for the snippet. – Anup Sharma Mar 26 '18 at 21:39
  • This is just works if you have easy typed properties, if you have a subclass instance eg. you should implement the iserialiable. – Bence Végert Jul 20 '18 at 12:56
10

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 }

Hossein Ebrahimi
  • 632
  • 10
  • 20
9
List<Book> books_2 = new List<Book>(books_2.ToArray());

That should do exactly what you want. Demonstrated here.

Virepri
  • 172
  • 1
  • 2
  • 1
    This works, but note that one can't just pass new List(books_2.ToArray()); to a function. It has to be done just like it's posted and then "books_2" could be passed. – user3496060 Jun 24 '18 at 06:49
  • 19
    This does not work for reference types. What you end up with is a pointer to the original. If you update a property on one you have now changed both! – jrandomuser Jul 06 '18 at 15:03
  • That's the cleanest solution to deep copy an array. Thanks man. You saved lot of my time. – Akash Sep 23 '18 at 12:21
  • 2
    As this does not work for reference types, why not just call `ToList()` on a list of value types, I don't see a difference. – Hossein Ebrahimi Jun 06 '21 at 18:16
2

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();
PaulG
  • 13,871
  • 9
  • 56
  • 78
Nate
  • 761
  • 1
  • 8
  • 27
2
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;
    }
}
Kanad Mehta
  • 371
  • 3
  • 4
0

If the Array class meets your needs, you could also use the List.ToArray method, which copies elements to a new array.

Reference: http://msdn.microsoft.com/en-us/library/x303t819(v=vs.110).aspx

JHaps
  • 41
  • 3
-3

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
  • 1
    This is not a copy, rather a shadow list with references to the same items in the list. Modifying an item in List A will modify the same item in List B because they are the same. – Youp Bernoulli Jul 15 '21 at 09:02