2012-12-22 45 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!深拷貝被創建!