Search code examples
c#deep-copy

Why doesn't this method produce a deep copy of the List?


In this thread:
How create a new deep copy (clone) of a List<T>?

the example of the person asking is how to create a deep copy of their List. I don't see anyone providing insight as to why it didn't work. Why did the new list behave as a reference to the old one? (Or at least it's elements).

I understand how references work in C++, and I thought the way they behave in C# is similar. But maybe I am missing something. I am still new to C#.

Here is a simplified version you can paste and run:

namespace OOPtest1
{
    internal class Program
    {
        public class Book
        {
            public string title = "";

            public Book(string title)
            {
                this.title = title;
            }
        }
        
       
        static void Main(string[] args)
        {
            //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"; // this changes the books_1 element as well
            books_2[1].title = "Six"; // this changes the books_1 element as well

        }   
    
    }
}

I expected this to behave like a completely different collection.


Solution

  • Since it seems there is some familiarity here with C++, I want to open up talking about struct vs class types. It's been a looong time, but IIRC in C++ the main difference between a struct and class is accessor defaults (public vs private). In C#, the default for both is internal, which is closer to private, but there is instead a semantic difference for struct (value type) vs class (reference type). It is strongly suggested to prefer using class as much as possible, and reserve struct for types that are both small and immutable.

    I start here, because it will be helpful for understanding some of what comes next, and because you could get closer to the behavior you expected by defining the Book type as a struct instead of a class. But again: this is not recommended.

    Going further, and knowing that Book is a reference type, C# does not make the assumption that copying the memory data of a reference type object is enough to deep copy the object. Think open streams, file handles, com/serial ports, database connections, references to other memory/objects, items that expect unique guid members, etc. If you need to be able to take deep copies, you must provide the code to do that yourself, and there is no language support for the equivalent to a C++ copy constructor.

    Therefore, when creating the books_2 list based on books_1, while it does make a copy of the internal collection in books_1, only the references are copied. Each item in books_2 initially refers to the same object as the equivalent item in books_1.

    I say "initially", because you can of course add and remove entire items from the books_2 list without effecting books_1. In this way, you do have a copy of the initial list; the books_2 list is its own object, and not just a reference to the same list as books_1. But since the list is for a reference type you only have copies of the references, and underlying objects are shared.

    Therefore, in the original code, if you want to change the items for books_2[0] and books_2[1], you could do it like this:

    books_2[0] = new Book("Five"); 
    books_2[1] = new Book("Six"); 
    

    This would leave books_1 completely unchanged. However, the [2] and [3] indexes would still share the same objects between the two lists.

    If you really wanted to fully deep copy the entire list, you might do this:

    var books_2 = books_1.Select(b => new Book(b.title)).ToList();