c# - without - ¿Cómo crear una nueva copia profunda(clon) de una Lista<T>?
c# clone list without reference (7)
En el siguiente fragmento de código,
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;
}
}
}
Utilizo un tipo de objeto Book
para crear una List<T>
y la rellene con algunos elementos, dándoles un título único (de ''uno'' a ''cinco'').
Luego creo List<Book> books_2 = new List<Book>(books_1)
.
Desde este punto, sé que es un clon del objeto de la lista, PERO los objetos del libro de book_2
todavía son una referencia de los objetos del libro en books_1
. Se ha probado haciendo cambios en los dos primeros elementos de books_2
y luego verificando esos mismos elementos de book_1
en un TextBox
.
books_1[0].title and books_2[1].title
han sido cambiados a los nuevos valores de books_2[0].title and books_2[1].title
.
AHORA LA PREGUNTA
¿Cómo creamos una nueva copia de una List<T>
? La idea es que books_1
y books_2
vuelvan completamente independientes entre sí.
Estoy decepcionado de que Microsoft no haya ofrecido una solución ordenada, rápida y fácil como la que Ruby está haciendo con el método clone()
.
Lo que sería realmente increíble de los ayudantes es usar mi código y modificarlo con una solución viable para que pueda compilarse y funcionar. Creo que realmente ayudará a los novatos a tratar de comprender las soluciones ofrecidas para este problema.
EDITAR: Tenga en cuenta que la clase de Book
podría ser más compleja y tener más propiedades. Traté de mantener las cosas simples.
Estoy decepcionado de que Microsoft no haya ofrecido una solución ordenada, rápida y fácil como la que Ruby está haciendo con el método
clone()
.
Excepto que no crea una copia profunda, crea una copia superficial.
Con la copia profunda, debe tener siempre cuidado, qué es exactamente lo que quiere copiar. Algunos ejemplos de posibles problemas son:
- Ciclo en el gráfico de objetos. Por ejemplo,
Book
tiene unAuthor
yAuthor
tiene una lista de susBook
s. - Referencia a algún objeto externo. Por ejemplo, un objeto puede contener una
Stream
abierta que escribe en un archivo. - Eventos. Si un objeto contiene un evento, prácticamente cualquiera podría suscribirse. Esto puede ser especialmente problemático si el suscriptor es algo así como una
Window
GUI.
Ahora, básicamente hay dos formas de clonar algo:
- Implemente un método
Clone()
en cada clase que necesite clonar. (También hayICloneable
interfazICloneable
, pero no debe usar eso, ya que el uso de una interfazICloneable<T>
personalizada como Trevor sugiere está bien). Si sabe que todo lo que necesita es crear una copia superficial de cada campo de esta clase, podría usarMemberwiseClone()
para implementarlo. Como alternativa, puede crear un "constructor de copias":public Book(Book original)
. - Use la serialización para serializar sus objetos en un
MemoryStream
y luego deserializarlos. Esto requiere que marque cada clase como[Serializable]
y también se puede configurar exactamente (y cómo) se debe serializar. Pero esto es más una solución "rápida y sucia", y probablemente también sea menos eficiente.
Bien,
Si marcas todas las clases involucradas como serializables, puedes:
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);
}
Fuente:
Como Clone
devolvería una instancia de objeto de Libro, ese objeto primero tendría que ser ToList
a un Libro antes de que pueda invocar a ToList
en él. El ejemplo anterior debe escribirse como:
List<Book> books_2 = books_1.Select(book => (Book)book.Clone()).ToList();
Cree una ICloneable<T>
genérica ICloneable<T>
que implemente en su clase Book
para que la clase sepa cómo crear una copia de sí mismo.
public interface ICloneable<T>
{
T Clone();
}
public class Book : ICloneable<Book>
{
public Book Clone()
{
return new Book { /* set properties */ };
}
}
A continuación, puede utilizar los métodos linq o ConvertAll
mencionados por Mark.
List<Book> books_2 = books_1.Select(book => book.Clone()).ToList();
o
List<Book> books_2 = books_1.ConvertAll(book => book.Clone());
Necesita crear nuevos objetos de Book
luego ponerlos en una nueva List
:
List<Book> books_2 = books_1.Select(book => new Book(book.title)).ToList();
Actualización: Ligeramente más simple ... List<T>
tiene un método llamado ConvertAll
que devuelve una nueva lista:
List<Book> books_2 = books_1.ConvertAll(book => new Book(book.title));
Si la clase Array satisface sus necesidades, también puede usar el método List.ToArray, que copia elementos en una nueva matriz.
Referencia: http://msdn.microsoft.com/en-us/library/x303t819(v=vs.110).aspx
List<Book> books_2 = new List<Book>(books_2.ToArray());
Eso debería hacer exactamente lo que quieras. Demostrado aquí.