85

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,496
  • 3
  • 26
  • 38
TheScholar
  • 2,347
  • 5
  • 21
  • 25

11 Answers11

127

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
  • 767,688
  • 176
  • 1,542
  • 1,434
  • 46
    What if the Book object was more complex and had thousands of other properties? – TheScholar Dec 22 '12 at 23:26
  • 20
    +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
  • 14
    @TheScholar: Then it would be a badly designed class. Thousands of properties... are you serious? – Mark Byers Dec 22 '12 at 23:27
  • 10
    @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
  • 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
44

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
  • 15,778
  • 5
  • 43
  • 59
21

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
  • 225,720
  • 49
  • 378
  • 501
  • Why should you not use the default ICloneable interface but instead use a custom generic one? – Jan Jul 15 '21 at 08:42
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
  • 998
  • 10
  • 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
9

You can use this:

var newList= JsonConvert.DeserializeObject<List<Book>>(list.toJson());
Dominique
  • 13,061
  • 14
  • 45
  • 83
Thao Le
  • 91
  • 1
  • 1
8
List<Book> books_2 = new List<Book>(books_2.ToArray());

That should do exactly what you want. Demonstrated here.

Virepri
  • 137
  • 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
  • 16
    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
  • 1
    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
5

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
  • 360
  • 5
  • 13
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,531
  • 9
  • 55
  • 74
Nate
  • 699
  • 1
  • 7
  • 26
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
  • 361
  • 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
  • 43
  • 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. – Bernoulli IT Jul 15 '21 at 09:02