web-dev-qa-db-fra.com

Comment créer une nouvelle copie profonde (clone) d'une liste <T>?

Dans le morceau de code suivant, 

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

}

J'utilise un type d'objet Book pour créer un List<T> et je le remplis de quelques éléments en leur donnant un titre unique (de "un" à "cinq").

Ensuite, je crée List<Book> books_2 = new List<Book>(books_1)

À partir de là, je sais que c'est un clone de l'objet liste, MAIS les objets livre de book_2 sont toujours une référence des objets livre de books_1. Cela a été prouvé en apportant des modifications aux deux premiers éléments de books_2, puis en vérifiant ces mêmes éléments de book_1 dans une TextBox.

books_1[0].title and books_2[1].title ont bien été remplacés par les nouvelles valeurs de books_2[0].title and books_2[1].title.

MAINTENANT LA QUESTION

Comment créer une nouvelle copie papier d'un List<T>? L'idée est que books_1 et books_2 deviennent complètement indépendants l'un de l'autre.

Je suis déçu que Microsoft n’ait pas proposé de solution simple, rapide et simple comme le fait Ruby avec la méthode clone().

Ce qui serait vraiment génial de la part des assistants, c’est d’utiliser mon code et de le modifier avec une solution viable afin qu’il puisse être compilé et fonctionne. Je pense que cela aidera vraiment les débutants à comprendre les solutions proposées pour résoudre ce problème.

EDIT: Notez que la classe Book pourrait être plus complexe et avoir plus de propriétés. J'ai essayé de garder les choses simples.

53
TheScholar

Vous devez créer de nouveaux objets Book puis les placer dans une nouvelle List:

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

Mise à jour: un peu plus simple ... List<T> utilise une méthode appelée ConvertAll qui renvoie une nouvelle liste:

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

Créez une interface ICloneable<T> générique que vous implémentez dans votre classe Book afin que celle-ci sache créer une copie d'elle-même.

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

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

Vous pouvez ensuite utiliser les méthodes linq ou ConvertAll mentionnées par Mark.

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

ou

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

Je suis déçu que Microsoft n’ait pas proposé de solution simple, rapide et simple comme le fait Ruby avec la méthode clone().

Sauf que not crée une copie en profondeur, il crée une copie superficielle.

Avec la copie en profondeur, vous devez toujours faire attention à ce que vous voulez exactement copier. Voici quelques exemples de problèmes possibles:

  1. Cycle dans le graphe d'objets. Par exemple, Book a un Author et Author a une liste de ses Books.
  2. Référence à un objet externe. Par exemple, un objet peut contenir open Stream qui écrit dans un fichier.
  3. Événements. Si un objet contient un événement, pratiquement tout le monde peut y être abonné. Cela peut devenir particulièrement problématique si l'abonné est quelque chose comme une interface graphique Window.

Maintenant, il y a fondamentalement deux manières de cloner quelque chose:

  1. Implémentez une méthode Clone() dans chaque classe que vous avez besoin de cloner. (Il y a aussi une interface ICloneable, mais vous devriez pas l'utiliser; utiliser une interface personnalisée ICloneable<T> comme suggéré par Trevor est acceptable.) Si vous savez qu'il suffit de créer une copie superficielle de chaque champ de cette classe, vous pouvez utiliser MemberwiseClone() pour le mettre en œuvre. Vous pouvez également créer un «constructeur de copie»: public Book(Book original).
  2. Utilisez la sérialisation pour sérialiser vos objets dans une MemoryStream, puis les désérialiser. Cela nécessite que vous marquiez chaque classe en tant que [Serializable] et vous pouvez également configurer quoi (et comment) exactement doit être sérialisé. Mais il s’agit plus d’une solution «rapide et sale», et sera probablement aussi moins performante.
17
svick
List<Book> books_2 = new List<Book>(books_2.ToArray());

Cela devrait faire exactement ce que vous voulez. Démonstré ici.

7
Virepri

Bien,

Si vous marquez toutes les classes impliquées comme sérialisables, vous pouvez:

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

La source: 

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

6
gatsby

Étant donné que Clone renverrait une instance d'objet de Book, cet objet devrait tout d'abord être converti en un livre avant de pouvoir appeler ToList. L'exemple ci-dessus doit être écrit comme suit:

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

Si la classe Array répond à vos besoins, vous pouvez également utiliser la méthode List.ToArray, qui copie des éléments dans un nouveau tableau. 

Référence: http://msdn.Microsoft.com/en-us/library/x303t819(v=vs.110).aspx

2
JHaps

Vous pouvez utiliser ceci:

var newList= JsonConvert.DeserializeObject<List<Book>>(list.toJson());
1
Thao Le
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;
    }
}
1
user3192640