如何创建列表的深层副本 collection

How can I create a deep copy of my list collection

假设我有以下 class:

public class Author
{
    public int ID {get; private set;}
    public string firstName {get; private set;}
    public string lastName {get; private set; }

    public Author(int id, string firstname, string lastname)
    {
        this.ID = ID;
        this.firstName = firstname;
        this.lastName = lastName;
    }

    public static Author Clone(Author clone)
    {
        Author copyAuth = new Author(clone.ID, clone.firstName, clone.lastName);
        return copyAuth;
    }
}

public class Book
{
    public string bookTitle {get; private set;}
    private List<Author> authors;
    private List<Author> copyofAuthors;
    public string ISBN {get; private set; }

    public Book(string bookTitle, List<Author> authors, string ISBN)
    {
        copyofAuthors = new List<Author>();
        this.bookTitle = bookTitle;
        this.ISBN = ISBN;
        //How do I create a deep copy of my authors List?

        foreach(Author copy in authors)
        {
            Author addAuthors = Author.Clone(copy);
            copyofAuthors.Add(addAuthors);
        }
    }
}

如何创建 List<Authors> collection 的深拷贝?我已经阅读了 Whosebug 上建议序列化的其他页面,以及我不熟悉且看起来令人困惑的建议。

我按照 创建了我的克隆方法。

问题一:

上面的实现算深拷贝吗?如果是这样,这样做可以吗?我的意思是,构造函数中的 foreach 循环将作者复制到新列表 collection.

问题二:

如果我修改作者副本 collection 中的任何内容,它 不再引用 原始 collection 正确吗?所以原来的collection应该保持不变?

更新#1:

    public List<Author> Authors
    {
        get
        {
            return returnAuthors(authors);
        }

    }

    private List<Author> returnAuthors(List<Author> copyList)
    {
        List<Author> getAuthors = new List<Author>();

        foreach(Author copy in copyList){
            getAuthors.Add(Author.Clone(copy));
        }

        return getAuthors;
    }

我是否正确实施了我的 getter collection,以便当它 returns 列表 collection, 它独立于原始 collection?因此,从 getter 返回的 collection 所做的任何更改都不会反映在 原始 collection 中,对吗?

更新#2:

使用 ReadOnlyCollection

    public class Book
    {

        public string bookTitle {get; private set;}
        private ReadOnlyCollection<Author> authors;
        public string ISBN {get; private set; }


        public Book(string bookTitle, ReadOnlyCollection<Author> authors, string ISBN)
        {

            this.bookTitle = bookTitle;
            this.ISBN = ISBN;
            //Is it okay to do this? 
            this.authors = authors;

        }

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

        }

    }

如果你有很多属性,写这个会很无聊:

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

查看这个关于如何实现 ICloneable 接口的小例子:

这个例子来自: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();

问题一: 是的,这会执行深拷贝。我建议使用 ICloneable 接口而不是这个静态函数,因为它是标准的。

问题二: 是的,原来的 collection 不会改变,因为您正在使用新的 collection object。

问题三: 如果你 return 中的 collection 中的 getter 那么有人可以改造它。要么你 return 每次有人想要得到它时 collection 的新副本,要么你将它包装在一个容器中,该容器将具有私有只读 collection 并且不会暴露 collection(但可以枚举)。

在你的情况下不确定你是否希望作者是独立的。鉴于它们已经只能私下设置,分享它们的更新可能会有用。您唯一需要保持独立的是 collection。所以也许不需要克隆作者。

很难在评论中解决这个问题。

  • 从您的作者中删除克隆方法 class。这是没用的。

在你的书中class,你有两个问题需要解决。

  • 构造函数采用作者列表,但传入它的调用者可能会更改它。如果我们只是复制引用,那么调用者可能会无意中更改书中保存的列表。

  • 本书返回作者名单。如果来电者再次向该列表添加内容,则他们已经改变了这本书。

您可以使用不可变集合解决这两个问题。如果您还没有使用 NuGet 下载不可变集合库。

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;
  }
}

那里。现在您为作者的 序列 创建了一个 copy,因此如果调用者更改了该序列,不用担心,您有一个副本。然后你分发一个由不可变集合实现的 IReadOnlyList,因此没有人可以更改它。

结合更多的东西。你问"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);
        }
    }

(已删除无关内容)。

不,这不太正确,有几个原因。首先,只读集合只是可变集合的 包装器 。您仍然处于调用者控制基础集合的情况,因此可以更改它。

其次,打字不太好;您不能将 ReadOnlyCollection 转换为列表。

我知道这很令人困惑。这里有一个微妙的区别。一个只读集合就是:只能阅读它。不代表别人写不出来!这样的集合仍然可变,只是不可变。不可变集合是真正不可变的;没有人可以改变它。

接下来:你做得很好,作者和书都是不可变的。但是如果你想改变它怎么办?正如您所注意到的,改变一本不变的书意味着制作一本新书。但是你已经有了一本旧书;你怎么能有效地做到这一点?常见的模式是:

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, "");
}

现在您可以这样做了:

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

以此类推