如何创建List <T>的新深层副本(克隆)?

The*_*lar 59 c# clone list deep-copy

在下面的代码中,

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

}
Run Code Online (Sandbox Code Playgroud)

我使用一个Book对象类型来创建一个List<T>,我用几个项目填充它,给它们一个唯一的标题(从'one'到'five').

然后我创造List<Book> books_2 = new List<Book>(books_1).

从这一点来说,我知道它是列表对象的一个​​克隆,但是书中的对象book_2仍然是书中对象的引用books_1.它是通过使上的两个第一要素的变化证明books_2,然后检查这些相同的元素book_1TextBox.

books_1[0].title and books_2[1].title确实已经改变了新的价值观books_2[0].title and books_2[1].title.

现在的问题

我们如何创建一个新的硬拷贝List<T>?这个想法是,books_1books_2成为完全相互独立的.

我很失望微软没有像Ruby那样提供一个简洁,快速,简单的解决方案clone().

帮助者真正棒极了的是使用我的代码并使用可行的解决方案来改变它,以便它可以编译和工作.我认为这将真正帮助新手尝试理解为此问题提供的解决方案.

编辑:请注意,Book该类可能更复杂,具有更多属性.我试着保持简单.

Mar*_*ers 96

您需要创建新Book对象,然后将它们放入新对象中List:

List<Book> books_2 = books_1.Select(book => new Book(book.title)).ToList();
Run Code Online (Sandbox Code Playgroud)

更新:稍微简单... List<T>有一个调用的方法ConvertAll返回一个新列表:

List<Book> books_2 = books_1.ConvertAll(book => new Book(book.title));
Run Code Online (Sandbox Code Playgroud)

  • 如果Book对象更复杂并且有数千个其他属性怎么办? (31认同)
  • +1,@ TheScholar - 比您创建复制构造函数或实现其他方法来创建对象的深层副本. (17认同)
  • @TheScholar:那将是一个设计糟糕的课程.成千上万的房产......你认真吗? (7认同)
  • @MarkByers当然是一个演员的形象.我的示例提供了一个属性,以保持示例简单易读. (7认同)
  • 新问题可以简化为"如何在C#中复制单个对象?" (5认同)
  • @RoberF.`ToList()`是不够的,因为OP要求深层复制.单独的`ToList()`将是一个浅拷贝."选择"不仅复制列表,还复制其中的对象. (3认同)
  • @Vidyesh - 与什么相比太贵了?规范是创建一个列表*其中包含新对象*。做到这一点的唯一方法是*创建新对象*。如果您*不需要*需要“新对象”,那么您不需要“深层复制” - 您只需执行`.ToList()`即可。但这不是本次问答的主题。 (2认同)

Tre*_*ley 31

创建一个ICloneable<T>Book类中实现的通用接口,以便类知道如何创建自身的副本.

public interface ICloneable<T>
{
    T Clone();
}

public class Book : ICloneable<Book>
{
    public Book Clone()
    {
        return new Book { /* set properties */ };
    }
}
Run Code Online (Sandbox Code Playgroud)

然后,您可以使用ConvertAllMark提到的linq或方法.

List<Book> books_2 = books_1.Select(book => book.Clone()).ToList();
Run Code Online (Sandbox Code Playgroud)

要么

List<Book> books_2 = books_1.ConvertAll(book => book.Clone());
Run Code Online (Sandbox Code Playgroud)


svi*_*ick 17

我很失望微软没有像Ruby那样提供一个简洁,快速,简单的解决方案clone().

除非创建深层副本,否则会创建浅层副本.

通过深度复制,您必须始终小心,您想要复制什么.可能的问题的一些例子是:

  1. 在对象图中循环.例如,Book有一个Author并且Author有一个他Book的列表.
  2. 引用一些外部对象.例如,一个对象可以包含Stream写入文件的open .
  3. 活动.如果一个对象包含一个事件,几乎任何人都可以订阅它.如果订户类似于GUI,这可能会特别成问题Window.

现在,基本上有两种克隆方法:

  1. Clone()在每个需要克隆的类中实现一个方法.(也有ICloneable接口,但你应该不会使用;使用自定义ICloneable<T>.接口特雷弗建议是好的)如果你知道所有你需要的是创建该类的每个字段的一个浅拷贝,你可以使用MemberwiseClone()来实现它.作为替代方案,您可以创建一个"复制构造函数":public Book(Book original).
  2. 使用序列化将对象序列化为a MemoryStream然后反序列化它们.这要求您将每个类标记为[Serializable],并且还可以配置应该序列化的确切(以及如何).但这更像是一种"快速而肮脏"的解决方案,并且很可能也会降低性能.


Hos*_*imi 12

C# 9记录with 表达式可以使它变得更容易一些,特别是当您的类型有很多属性时。

你可以使用类似的东西:

var books2 = books1.Select(b => b with { }).ToList();
Run Code Online (Sandbox Code Playgroud)

我这样做了一个例子

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

输出为:(对 Books2 中的对象进行的更改不会影响 Books1 中的原始对象)

书籍 1 包含:

书 { 名称 = Book1.1 }

书 { 名称 = Book1.2 }

书 { 名称 = Book1.3 }

Books2 包含:

书 { 名称 = 已更改 }

书 { 名称 = 已更改 }

书 { 名称 = Book1.3 }


小智 10

List<Book> books_2 = new List<Book>(books_2.ToArray());
Run Code Online (Sandbox Code Playgroud)

这应该完全符合你的要求.在这里展示.

  • 这不适用于引用类型。您最终得到的是原始文档的指针。如果您更新了其中一个的属性,那么您现在已经更改了两者! (4认同)
  • 由于这不适用于引用类型,为什么不在值类型列表上调用“ToList()”,我没有看到区别。 (2认同)

小智 9

你可以使用这个:

var newList= JsonConvert.DeserializeObject<List<Book>>(list.toJson());
Run Code Online (Sandbox Code Playgroud)


gat*_*sby 8

好,

如果将所有涉及的类标记为可序列化,则可以:

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);      
} 
Run Code Online (Sandbox Code Playgroud)

资源:

https://social.msdn.microsoft.com/Forums/en-US/5c9b4c31-850d-41c4-8ea3-fae734b348c4/copy-listsomeobject-to-clone-list?forum=csharpgeneral