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

Posted

技术标签:

【中文标题】如何创建 List<T> 的新深层副本(克隆)?【英文标题】:How create a new deep copy (clone) of a List<T>? 【发布时间】:2012-12-10 01:16:13 【问题描述】:

在下面这段代码中,

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;
        
    


我使用Book 对象类型创建List&lt;T&gt;,并在其中填充一些项目,并赋予它们一个唯一的标题(从“一”到“五”)。

然后我创建List&lt;Book&gt; books_2 = new List&lt;Book&gt;(books_1)

从这一点上,我知道它是列表对象的克隆,但是来自book_2 的书籍对象仍然是来自books_1 的书籍对象的引用。通过对books_2 的前两个元素进行更改,然后在TextBox 中检查book_1 的相同元素,可以证明这一点。

books_1[0].title and books_2[1].title 确实已更改为 books_2[0].title and books_2[1].title 的新值。

现在的问题

我们如何创建List&lt;T&gt; 的新硬拷贝?这个想法是books_1books_2 变得完全相互独立。

我很失望微软没有像 Ruby 那样提供简洁、快速和简单的解决方案,使用 clone() 方法。

帮助程序真正令人敬畏的是使用我的代码并使用可行的解决方案对其进行更改,以便可以编译和工作。我认为这将真正帮助新手了解针对此问题提供的解决方案。

编辑:请注意,Book 类可能更复杂并具有更多属性。我尽量保持简单。

【问题讨论】:

类似CopyTo? 这种类型的复制通常称为deep copy。如果您觉得“硬拷贝”与您实际需要的不同 - 请撤消我的编辑并添加您对该术语的定义。 A hard copy 表示您将其打印到物理纸上。 请查看之前提出的有关深拷贝的其他相关问题 - ***.com/questions/tagged/… 我的意思确实是深拷贝,对不起我还是个新手。 【参考方案1】:

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

List<Book> books_2 = books_1.Select(book => new Book(book.title)).ToList();

更新:稍微简单一点...List&lt;T&gt; 有一个名为 ConvertAll 的方法,它返回一个新列表:

List<Book> books_2 = books_1.ConvertAll(book => new Book(book.title));

【讨论】:

如果 Book 对象更复杂并且有数千个其他属性怎么办? +1, @TheScholar - 你要么创建复制构造函数,要么实现其他方式来创建对象的深层副本。 @TheScholar:那么这将是一个设计糟糕的课程。数以千计的房产……你是认真的吗? @MarkByers 这当然是一个比喻。为了使示例简单易读,我的示例提供了一个属性。 新问题可以简化为“如何在C#中复制单个对象?”【参考方案2】:

创建一个在Book 类中实现的通用ICloneable&lt;T&gt; 接口,以便该类知道如何创建自身的副本。

public interface ICloneable<T>

    T Clone();


public class Book : ICloneable<Book>

    public Book Clone()
    
        return new Book  /* set properties */ ;
    

然后您可以使用 Mark 提到的 linq 或 ConvertAll 方法。

List<Book> books_2 = books_1.Select(book => book.Clone()).ToList();

List<Book> books_2 = books_1.ConvertAll(book => book.Clone());

【讨论】:

【参考方案3】:

我很失望微软没有像 Ruby 那样提供简洁、快速和简单的解决方案,使用 clone() 方法。

除了创建一个深拷贝,它创建一个浅拷贝。

对于深度复制,您必须始终小心,您究竟想要复制什么。一些可能的问题示例如下:

    对象图中的循环。例如,Book 有一个AuthorAuthor 有一个他的Books 列表。 对某些外部对象的引用。例如,一个对象可能包含打开的Stream 写入文件。 事件。如果一个对象包含一个事件,几乎任何人都可以订阅它。如果订阅者是 GUI Window 之类的东西,这可能会特别成问题。

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

    在您需要克隆的每个类中实现Clone() 方法。 (也有 ICloneable 接口,但您应该使用它;使用 Trevor 建议的自定义 ICloneable&lt;T&gt; 接口是可以的。)如果您知道您所需要的只是创建一个浅拷贝对于此类的每个字段,您可以使用MemberwiseClone() 来实现它。作为替代方案,您可以创建一个“复制构造函数”:public Book(Book original)。 使用序列化将您的对象序列化为MemoryStream,然后将它们反序列化。这需要您将每个类标记为[Serializable],并且还可以配置应该序列化的确切内容(以及如何)。但这更像是一种“快速而肮脏”的解决方案,而且很可能性能也较差。

【讨论】:

为什么不应该使用默认的 ICloneable 接口,而是使用自定义的通用接口?【参考方案4】:

嗯,

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

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

来源:

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

【讨论】:

当其他一切都失败时,这个帮助了我。我有一个对象类型属性,没有其他方法可以复制该属性。谢谢sn-p。 如果你有简单的类型属性,如果你有一个子类实例,例如。你应该实现可串行化。【参考方案5】:

你可以用这个:

var newList= JsonConvert.DeserializeObject<List<Book>>(list.toJson());

【讨论】:

【参考方案6】:
List<Book> books_2 = new List<Book>(books_2.ToArray());

这应该完全符合您的要求。 Demonstrated here.

【讨论】:

这行得通,但请注意不能只通过 new List(books_2.ToArray());到一个函数。它必须像发布的那样完成,然后可以传递“books_2”。 这不适用于引用类型。你最终得到的是一个指向原始的指针。如果您更新一个属性,您现在已经更改了两个! 这是深度复制数组的最简洁的解决方案。谢啦。你节省了我很多时间。 由于这不适用于引用类型,为什么不在值类型列表上调用ToList(),我看不出有什么区别。【参考方案7】:

C# 9 records 和 with expressions 可以让它更容易一些,尤其是当你的类型有很多属性时。

你可以使用类似的东西:

var books2 = books1.Select(b => b with  ).ToList();

我这样做是作为一个示例

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

输出结果是:(对 Books2 中的对象所做的更改不会影响 Books1 中的原始对象)

Books1 包含:

图书 名称 = Book1.1

图书 名称 = Book1.2

图书 名称 = Book1.3

Books2 包含:

图书 名称 = 已更改

图书 名称 = 已更改

图书 名称 = Book1.3

【讨论】:

【参考方案8】:

因为Clone 会返回一个Book 的对象实例,所以在调用ToList 之前,该对象首先需要转换为Book。上面的例子需要写成:

List<Book> books_2 = books_1.Select(book => (Book)book.Clone()).ToList();

【讨论】:

【参考方案9】:
public static class Cloner

    public static T Clone<T>(this T item)
    
        FieldInfo[] fis = item.GetType().GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
        object tempMyClass = Activator.CreateInstance(item.GetType());
        foreach (FieldInfo fi in fis)
        
            if (fi.FieldType.Namespace != item.GetType().Namespace)
                fi.SetValue(tempMyClass, fi.GetValue(item));
            else
            
                object obj = fi.GetValue(item);
                if (obj != null)
                    fi.SetValue(tempMyClass, obj.Clone());
            
        
        return (T)tempMyClass;
    

【讨论】:

【参考方案10】:

如果 Array 类满足您的需求,您还可以使用 List.ToArray 方法,该方法将元素复制到新数组中。

参考:http://msdn.microsoft.com/en-us/library/x303t819(v=vs.110).aspx

【讨论】:

在我的例子中,这是最简单的方法。例如: var tagsToRead = new List( tagsFailed.ToArray()); 除了数组中的对象仍然是对原始列表中的项目的引用。【参考方案11】:

直接复制任何通用列表的简单方法:

List<whatever> originalCopy=new List<whatever>();//create new list
originalCopy.AddRange(original);//perform copy of original list

【讨论】:

这不是一个副本,而是一个影子列表,其中引用了列表中的相同项目。修改列表 A 中的项目将修改列表 B 中的相同项目,因为它们是相同的。

以上是关于如何创建 List<T> 的新深层副本(克隆)?的主要内容,如果未能解决你的问题,请参考以下文章

如何在 Java 中制作 ArrayList<Integer> 的深层副本? [复制]

QVector::replace() 是不是创建深层副本?

Angular - 达到 10 次 $digest() 迭代。创建数组的深层副本时中止

如何创建保持相同顺序的链接列表的深层副本

C# 是通过引用还是作为副本将 List<T> 传递给方法? [复制]

如何制作对象的深层副本?