如何创buildList <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
对象types来创build一个List<T>
并用一些项目给它们填充一个唯一的标题(从“一”到“五”)。
然后我创buildList<Book> books_2 = new List<Book>(books_1)
。
从这一点来说,我知道这是一个列表对象的克隆,但book_2
中的book对象仍然是book_2
中book对象的books_1
。 通过对books_2
的两个第一个元素进行books_2
,然后在TextBox
检查book_1
相同元素, book_1
了这一点。
books_1[0].title and books_2[1].title
确实已经改变为books_2[0].title and books_2[1].title
的新值。
现在问题
我们如何创buildList<T>
的新硬拷贝? 这个想法是books_1
和books_2
完全相互独立。
我感到很失望,微软并没有提供像Ruby一样使用clone()
方法的简洁快捷的解决scheme。
佣工真的很棒,就是使用我的代码,并用一个可行的解决scheme来改变它,这样可以编译和工作。 我认为这将真正帮助新手理解为这个问题提供的解决scheme。
编辑:请注意Book
类可能会更复杂,有更多的属性。 我试图保持简单。
您需要创build新的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));
创build一个在Book
类中实现的通用ICloneable<T>
接口,以便该类知道如何创build自己的副本。
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());
我感到很失望,微软并没有提供像Ruby一样使用
clone()
方法的简洁快捷的解决scheme。
除了不创build深层复制,它会创build一个浅层副本。
通过深度复制,您必须小心谨慎,您要复制到底是什么。 可能的问题的一些例子是:
- 在对象图中循环。 例如,
Book
有一个Author
和Author
有他的Book
的列表。 - 引用一些外部对象。 例如,一个对象可能包含写入文件的开放
Stream
。 - 活动。 如果一个对象包含一个事件,几乎任何人都可以订阅它。 如果用户像GUI
Window
一样,这可能会特别成问题。
现在,基本上有两种如何克隆的方法:
- 在你需要克隆的每个类中实现一个
Clone()
方法。 (也有ICloneable
接口,但是你不应该使用它;使用习惯ICloneable<T>
接口作为Trevorbuild议是好的。)如果你知道你需要的是创build这个类的每个字段的浅拷贝,可以使用MemberwiseClone()
来实现它。 作为替代,您可以创build一个“复制构造函数”:public Book(Book original)
。 - 使用序列化将对象序列化到
MemoryStream
,然后反序列化它们。 这要求您将每个类标记为[Serializable]
,并且还可以configuration序列化的方式(以及如何)。 但是这更多的是一个“快速和肮脏的”解决scheme,而且很可能性能也不高。
好,
如果您将所有涉及的类标记为可序列化,则可以:
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); }
资源:
如果Array类满足您的需要,您也可以使用List.ToArray方法,它将元素复制到一个新的数组中。
参考: http : //msdn.microsoft.com/en-us/library/x303t819(v=vs.110).aspx
由于Clone
会返回Book的一个对象实例,因此该对象首先需要转换为Book,然后才能调用ToList
。 上面的例子需要写成:
List<Book> books_2 = books_1.Select(book => (Book)book.Clone()).ToList();