2012-12-22 41 views
40

在下面的一段代码,如何创建列表的新深层副本(克隆)<T>?

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<T>,我有几个项目给他们一个独特的标题(从“一”到“十二五”)来填充它。

然后我创建List<Book> books_2 = new List<Book>(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<T>的一个新的硬拷贝?这个想法是,books_1books_2变得完全独立于彼此。

我很失望微软并没有提供一个整洁,快速和简单的解决方案,就像Ruby正在使用clone()方法一样。

会从佣工真正真棒什么是用我的代码和一个可行的方案改变它,所以它可以编译和工作。我认为这将真正帮助新手试图理解为这个问题提供的解决方案。

编辑:请注意,Book类可能更加复杂,并有更多的属性。我试图保持简单。

+0

类似'CopyTo'? –

+0

这种类型的复制通常称为[深层复制](http://en.wikipedia。组织/维基/ DEEP_COPY#DEEP_COPY)。如果您觉得“硬拷贝”与您实际需要的东西不同 - 请撤消我的编辑并添加您对该术语的定义。 –

+1

[硬拷贝](http://en.wikipedia.org/wiki/Hard_copy)表示您将其打印到实际的纸张上。 –

回答

80

您需要创建新的对象Book然后把那些在新List

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

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

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

如果Book对象更复杂并且拥有数千个其他属性,该怎么办? – TheScholar

+11

+1,@TheScholar - 比你创建副本构造器或实现其他方式创建对象的深度副本。 –

+6

@TheScholar:那么这将是一个设计不好的班级。成千上万的物业......你认真吗? –

18

创建一个通用的ICloneable<T>界面,您可以在Book类中实现该界面,以便该类知道如何创建自己的副本。

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

酷!谢谢 ! –

+0

不错的解决方案的人,这节省了我重写大量的新代码。 – Mana

16

我很失望,微软并没有提供像Ruby整齐,快速和容易的解决方案与clone()方法做。

除了而不是创建一个深层副本,它会创建一个浅拷贝。

随着深度复制,您必须时刻谨慎,您要拷贝什么。可能的问题的一些示例如下:

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

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

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

由于Clone将返回Book的对象实例,因此该对象首先需要转换为Book,然后才能对其调用ToList。上述需求的例子可以写成:

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

如果Array类满足你的需求,你也可以使用List.ToArray方法,它的副本元素的新数组。

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

+2

在我的例子中这是最简单的方法。例如:var tagsToRead = new List (tagsFailed.ToArray()); –

+3

除了数组中的对象仍然是对原始列表中项目的引用。 –

3

好,

如果标记所有参与类为可序列化,您可以:

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

-2

或者你可以只只需添加范围即可k_2:

book_2.AddRange(book_1); 

Bang!深拷贝被创建!