How can I create a deep copy of my list collection

僤鯓⒐⒋嵵緔 提交于 2019-12-05 13:23:06

It's getting too hard to sort this out in the comments.

  • Remove the Clone method from your Author class. It is useless.

In your book class, you have two problems to solve.

  • The constructor takes a list of authors, but the caller who passed it in might change it. If we just copy the reference, then the caller can alter accidentally the list held by the book.

  • The book gives back a list of authors. If a caller adds something to that list then again, they've mutated the book.

You can solve both problems with an immutable collection. Download the immutable collections library using NuGet if you haven't got it already.

using System.Collections.Immutable;
...
public class Book
{
  public string bookTitle {get; private set;}
  private ImmutableList<Author> authors;
  public IReadOnlyList<Author> Authors { get { return authors; } }
  public string ISBN {get; private set; }

  public Book(string bookTitle, IEnumerable<Author> authors, string ISBN)
  {
    this.authors = ImmutableList<Author>.Empty.AddRange(authors);
    this.bookTitle = bookTitle;
    this.ISBN = ISBN;
  }
}

There. Now you make a copy of the sequence of authors, so if the caller changes that sequence, no worries, you have a copy. And you hand out an IReadOnlyList that is implemented by an immutable collection, so no one can change it.

Couple more things. You ask "is this right?"

public class Book
{
    private ReadOnlyCollection<Author> authors;
    public Book(ReadOnlyCollection<Author> authors)
    {
        //Is it okay to do this? 
        this.authors = authors;
    }

    public List<Author> Authors
    {
        get
        {   //Create a shallow copy
            return new ReadOnlyCollection<Author>(authors);
        }
    }

(Extraneous stuff removed).

No, that is not quite right, for a couple reasons. First, the read only collection is just a wrapper around a mutable collection. You are still in the situation where the caller controls the underlying collection, and therefore can change it.

Second, the typing doesn't quite work out; you can't convert a ReadOnlyCollection to a List.

This is confusing I know. There is a subtle distinction here. A read only collection is just that: you can only read it. It doesn't mean that someone else cannot write it! Such a collection is still mutable, it is just not mutable by you. An immutable collection is truly immutable; no one can change it.

Next: you are doing very well by making the author and the book both immutable. But what if you want to change it? As you note, to change an immutable book means making a new book. But you already have an old book; how can you do that efficiently? The common pattern is:

public class Book
{
  public string Title {get; private set;}
  private ImmutableList<Author> authors;
  public IReadOnlyList<Author> Authors { get { return authors; } }
  public string ISBN {get; private set; }

  public Book(string title, IEnumerable<Author> authors, string ISBN) : this(
    title, 
    ImmutableList<Author>.Empty.AddRange(authors),
    ISBN) {}

  public Book(string title, ImmutableList<Authors> authors, string ISBN) 
  {
    this.Title = title;
    this.Authors = authors;
    this.ISBN = ISBN;
  }
  public Book WithTitle(string newTitle)
  {
    return new Book(newTitle, authors, ISBN); 
  }
  public Book WithISBN(string newISBN)
  {
    return new Book(Title, authors, newISBN);
  }
  public Book WithAuthor(Author author)
  {
    return new Book(Title, authors.Add(author), ISBN);
  }
  public static readonly Empty = new Book("", ImmutableList<Author>.Empty, "");
}

Now you can do this:

Book tlotr = Book.Empty.WithAuthor("JRRT").WithTitle("The Lord Of The Rings");

And so on.

If you have many properties it ll be boring to write this:

new Author(clone.ID, clone.firstName, clone.lastName...);

Check this little exemple of how u can implement ICloneable interface :

This exemple comes from : http://csharp.2000things.com/2010/11/07/143-an-example-of-implementing-icloneable-for-deep-copies/

public class Person : ICloneable
{
    public string LastName { get; set; }
    public string FirstName { get; set; }
    public Address PersonAddress { get; set; }

    public object Clone()
    {
        Person newPerson = (Person)this.MemberwiseClone();
        newPerson.PersonAddress = (Address)this.PersonAddress.Clone();

        return newPerson;
    }
}

public class Address : ICloneable
{
    public int HouseNumber { get; set; }
    public string StreetName { get; set; }

    public object Clone()
    {
        return this.MemberwiseClone();
    }
}

Person herClone = (Person)emilyBronte.Clone();

Question 1: Yes this performs a deep copy. I recommend using the ICloneable interrface instead of this static function as it is standard.

Question 2: Yes the original collection will not change as you are using a new collection object.

Question 3: If you return the collection in the getter then someone can transform it. Either you return a new copy of the collection each time someone wants to get it or you wrap it in a container that would have a private readonly collection and does not expose the collection (but can be enumerable).

In your case not sure if you want the Author to be independent though. Given that they already can only be set privately, it might be useful to share updates on them. The only thing you need to keep independent is the collection. So maybe no need ot clone the Authors.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!