如何创建 List < 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>,并用一些项填充它,给它们一个唯一的标题(从“1”到“5”)。

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

从这一点来看,我知道它是 list 对象的克隆,但是来自 book_2的 book 对象仍然是来自 books_1中的 book 对象的引用。通过对 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类可以更复杂,具有更多的属性。

179574 次浏览

You need to create new Book objects then put those in a new List:

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

Update: Slightly simpler... List<T> has a method called ConvertAll that returns a new list:

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

Create a generic ICloneable<T> interface which you implement in your Book class so that the class knows how to create a copy of itself.

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


public class Book : ICloneable<Book>
{
public Book Clone()
{
return new Book { /* set properties */ };
}
}

You can then use either the linq or ConvertAll methods that Mark mentioned.

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

or

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

I'm disappointed Microsoft didn't offer a neat, fast and easy solution like Ruby are doing with the clone() method.

Except that does not create a deep copy, it creates a shallow copy.

With deep copying, you have to be always careful, what exactly do you want to copy. Some examples of possible issues are:

  1. Cycle in the object graph. For example, Book has an Author and Author has a list of his Books.
  2. Reference to some external object. For example, an object could contain open Stream that writes to a file.
  3. Events. If an object contains an event, pretty much anyone could be subscribed to it. This can get especially problematic if the subscriber is something like a GUI Window.

Now, there are basically two ways how to clone something:

  1. Implement a Clone() method in each class that you need cloned. (There is also ICloneable interface, but you should not use that; using a custom ICloneable<T> interface as Trevor suggested is okay.) If you know that all you need is to create a shallow copy of each field of this class, you could use MemberwiseClone() to implement it. As an alternative, you could create a “copy constructor”: public Book(Book original).
  2. Use serialization to serialize your objects into a MemoryStream and then deserialize them back. This requires you to mark each class as [Serializable] and it can also be configured what exactly (and how) should be serialized. But this is more of a “quick and dirty” solution, and will most likely also be less performant.

Since Clone would return an object instance of Book, that object would first need to be cast to a Book before you can call ToList on it. The example above needs to be written as:

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

If the Array class meets your needs, you could also use the List.ToArray method, which copies elements to a new array.

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

Well,

If you mark all involved classes as serializable you can :

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

Source:

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

List<Book> books_2 = new List<Book>(books_2.ToArray());

That should do exactly what you want. Demonstrated here.

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

You can use this:

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

Straight forward simple way to copy any generic list :

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

C# 9 records and with expressions can make it a little easier, especially if your type has many properties.

You can use something like:

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

I did this as an example:

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

And the output was: (Changes made to objects in Books2 did not affect original objects in Books1)

Books1 contains:

Book { Name = Book1.1 }

Book { Name = Book1.2 }

Book { Name = Book1.3 }

Books2 contains:

Book { Name = Changed }

Book { Name = Changed }

Book { Name = Book1.3 }