Подтвердить что ты не робот

Как создать новую глубокую копию (клон) списка <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 может быть более сложным и иметь больше свойств. Я старался, чтобы все было просто.

4b9b3361

Ответ 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], и также можно настроить, что именно (и как) должно быть сериализовано. Но это скорее "быстрое и грязное" решение, и, скорее всего, также будет менее эффективным.

Ответ 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;
    }
}