Как создать новую глубокую копию (клон) списка <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
, а затем проверку тех же элементов book_1
в TextBox
.
books_1[0].title and books_2[1].title
действительно были изменены на новые значения books_2[0].title and books_2[1].title
.
ТЕПЕРЬ ВОПРОС
Как создать новую копию List<T>
? Идея состоит в том, что books_1
и books_2
становятся полностью независимыми друг от друга.
Я разочарован, что Microsoft не предложила аккуратное, быстрое и простое решение, подобное Ruby, с помощью метода clone()
.
То, что действительно было бы полезно от помощников, - это использовать мой код и изменить его с помощью рабочего решения, чтобы его можно было скомпилировать и работать. Я думаю, что это действительно поможет новичкам, пытающимся понять предлагаемые решения для этой проблемы.
EDIT: обратите внимание, что класс Book
может быть более сложным и иметь больше свойств. Я старался, чтобы все было просто.
Ответы
Ответ 1
Вам нужно создать новые объекты 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));
Ответ 2
Создайте общий ICloneable<T>
интерфейс, который вы реализуете в своем классе Book
, чтобы класс знал, как создать копию самого себя.
public interface ICloneable<T>
{
T Clone();
}
public class Book : ICloneable<Book>
{
public Book Clone()
{
return new Book { /* set properties */ };
}
}
Затем вы можете использовать методы linq или ConvertAll
, о которых упоминал Mark.
List<Book> books_2 = books_1.Select(book => book.Clone()).ToList();
или
List<Book> books_2 = books_1.ConvertAll(book => book.Clone());
Ответ 3
Я разочарован тем, что Microsoft не предложила аккуратного, быстрого и простого решения, которое Ruby делает с помощью метода clone()
.
За исключением того, что не создается глубокая копия, создается мелкая копия.
При глубоком копировании вы должны быть всегда осторожны, что именно вы хотите скопировать. Некоторые примеры возможных проблем:
- Цикл в графе объектов. Например,
Book
имеет Author
и Author
имеет список своих Book
s.
- Ссылка на некоторый внешний объект. Например, объект может содержать открытый
Stream
, который записывает в файл.
- События. Если объект содержит событие, почти все могут быть подписаны на него. Это может стать особенно проблематичным, если подписчик - это что-то вроде GUI
Window
.
Теперь существуют два способа клонирования:
- Внедрить метод
clone()
в каждом классе, который вам нужно клонировать. (Существует также интерфейс ICloneable
, но вы не должны этого использовать; использование пользовательского интерфейса ICloneable<T>
, как предлагал Тревор, все в порядке.) Если вы знаете, что все, что вам нужно, это создать мелкую копию каждого поля этого класса, вы можете использовать MemberwiseClone()
для его реализации. В качестве альтернативы вы можете создать "конструктор копирования": public Book(Book original)
.
- Используйте сериализацию для сериализации ваших объектов в
MemoryStream
и затем десериализовать их обратно. Это требует, чтобы вы пометили каждый класс как [Serializable]
, и также можно настроить, что именно (и как) должно быть сериализовано. Но это скорее "быстрое и грязное" решение, и, скорее всего, также будет менее эффективным.
Ответ 4
List<Book> books_2 = new List<Book>(books_2.ToArray());
Это должно делать именно то, что вы хотите. Продемонстрировано здесь.
Ответ 5
Что ж,
Если вы помечаете все вовлеченные классы как сериализуемые, вы можете:
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
Ответ 6
Так как Clone
вернет экземпляр объекта Book, этот объект сначала нужно будет перенести в книгу, прежде чем вы сможете называть ToList
на нем. Приведенный выше пример должен быть записан как:
List<Book> books_2 = books_1.Select(book => (Book)book.Clone()).ToList();
Ответ 7
Если класс Array соответствует вашим потребностям, вы также можете использовать метод List.ToArray, который копирует элементы в новый массив.
Ссылка: http://msdn.microsoft.com/en-us/library/x303t819(v=vs.110).aspx
Ответ 8
Или вы просто можете добавить в book_2:
book_2.AddRange(book_1);
Взрыв! Создана глубокая копия.
Ответ 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;
}
}