【问题标题】:How can I create a deep copy of my list collection如何创建列表集合的深层副本
【发布时间】:2016-06-21 05:07:54
【问题描述】:

假设我有以下课程:

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&lt;Authors&gt; 集合的深层副本?我已经阅读了 StackOverFlow 上建议序列化的其他页面,以及我不熟悉且似乎令人困惑的建议。

我跟随this link 创建了我的克隆方法。

问题 1:

上述实现是否被视为深层复制?如果是这样,这样做可以吗?我的意思是,构造函数中的一个 foreach 循环将作者复制到一个新的列表集合中。

问题 2:

如果我修改了 copyofAuthor 集合中的任何内容,它不再引用原始集合正确吗?那么原始集合应该保持不变?

更新 #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 集合,以便当它返回 List 集合时,它独立于原始集合?因此,从 getter 返回的集合中所做的任何更改都不会反映在原始集合中,对吗?

更新 #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);
            }

        }

    }

【问题讨论】:

  • 我通常使用Activator.CreateInstance 来实现一个深度克隆的对象。当然,它使用反射,但对我的用例(通常希望克隆一个相当“浅”的数组)的性能影响可以忽略不计,并且代码表现力的收益超过了缺点。在您的情况下,静态克隆方法可能会满足您的目的。您是否正在寻找对您的技术的验证?如果是这样,静态Author.Clone() 方法并没有什么特别的问题。与 ICloneable 相比,您从中获得的好处是您得到了实际的Author,而不是object.
  • 您能否描述一下导致您相信深度复制是个好主意的场景?它在时间和内存上是昂贵的,而且很难做到正确。如果您想对原始列表进行多次独立编辑,则使用持久不可变集合可能会更好。特别是考虑到作者已经是不可变的;你为什么要首先克隆一个?
  • @K.AlanBates - 非常感谢,我正在寻找此方法的验证,您能否检查我的更新并请告知?我实现了一个吸气剂,想知道我是否正确地做到了。
  • 从阅读文档开始。
  • 如果您正在编写代码并且您不知道为什么要编写它,那么您今天开始编写代码太早了。 你知道你为什么要写代码之后再写代码。不要担心深拷贝与浅拷贝;这是一个很少重要的区别。考虑您的方法的使用者应该能够执行哪些操作。您考虑这些消费者是否可以改变您的收藏是绝对正确的!如果这是您关心的问题,那么不要让他们改变您的收藏。给他们一个不可变的集合。

标签: c# deep-copy shallow-copy


【解决方案1】:

在 cmets 中解决这个问题变得太难了。

  • 从 Author 类中删除 Clone 方法。这是没用的。

在你的读书课上,你有两个问题要解决。

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

  • 本书提供了作者列表。如果调用者再次将某些内容添加到该列表中,那么他们已经改变了这本书。

您可以使用不可变集合解决这两个问题。如果您还没有,请使用 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;
  }
}

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

结合更多的东西。你问“这样对吗?”

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 转换为 List。

我知道这很令人困惑。这里有一个微妙的区别。只读集合就是:只能读取它。这并不意味着 其他人 不会写!这样的集合仍然可变,它只是不可变。不可变集合是真正不可变的;没有人可以改变它。

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

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

等等。

【讨论】:

  • 非常感谢!只是为了我的理解。当你实现一个克隆方法时,只对可变对象这样做对吗?不可变意味着一旦创建状态,他们就无法更改状态,因此他们必须创建一个新的 Book 对象来更新任何内容,对吗?
  • @Svetlana:正确。让我向您展示一个常见的模式。
  • Java也有ImmutableList吗?
  • @Svetlana:在我的生活中,我已经编写了零行生产 Java 代码。我不知道。我建议你开始在互联网上寻找。
  • 只是更多的问题,假设我使用 List 的话,List authorList...将其设为私有,没有 getter,只需在构造函数中设置,用户还能修改设置后列出对象?
【解决方案2】:

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

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();

【讨论】:

    【解决方案3】:

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

    问题 2: 是的,当您使用新的集合对象时,原始集合不会改变。

    问题 3: 如果您在 getter 中返回集合,那么有人可以对其进行转换。每次有人想要获取集合时,您要么返回一个新的集合副本,要么将它包装在一个容器中,该容器具有私有只读集合并且不公开集合(但可以是可枚举的)。

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

    【讨论】:

    • 我贴的链接说ICloneable应该折旧,不要使用。
    • 你能告诉我如何实现一个复制集合的getter吗?我可以有一个循环的私有方法吗,就像我在构造函数中一样?
    • 如果我更新我的问题,你能看看并建议吗?
    猜你喜欢
    • 1970-01-01
    • 2023-03-05
    • 1970-01-01
    • 2012-05-16
    • 1970-01-01
    • 2011-07-03
    • 1970-01-01
    • 2019-02-25
    • 1970-01-01
    相关资源
    最近更新 更多