Hoe maak je een nieuwe diepe kopie (kloon) van een lijst<T>?

In het volgende stukje code,

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

Ik gebruik een Bookobjecttype om een ​​List<T>te maken en ik vul deze met een paar items waardoor ze een unieke titel krijgen (van ‘één’ tot ‘ vijf’).

Vervolgens maak ik List<Book> books_2 = new List<Book>(books_1).

Vanaf dit punt weet ik dat het een kloon is van het lijstobject, MAAR de boekobjecten van book_2zijn nog steeds een referentie van de boekobjecten in books_1. Het wordt bewezen door wijzigingen aan te brengen in de eerste twee elementen van books_2en vervolgens diezelfde elementen van book_1te controleren in een TextBox.

books_1[0].title and books_2[1].titlezijn inderdaad gewijzigd in de nieuwe waarden van books_2[0].title and books_2[1].title.

NU DE VRAAG

Hoe maken we een nieuwe papieren versie van een List<T>? Het idee is dat books_1en books_2volledig onafhankelijk van elkaar worden.

Ik ben teleurgesteld dat Microsoft geen nette, snelle en gemakkelijke oplossing heeft aangeboden zoals Ruby doet met de Clone()-methode.

Wat echt geweldig zou zijn van helpers, is om mijn code te gebruiken en deze te wijzigen met een werkbare oplossing, zodat deze kan worden gecompileerd en werken. Ik denk dat het nieuwkomers echt zal helpen om de aangeboden oplossingen voor dit probleem te begrijpen.

EDIT: Houd er rekening mee dat de klasse Bookcomplexer kan zijn en meer eigenschappen kan hebben. Ik heb geprobeerd het simpel te houden.


Antwoord 1, autoriteit 100%

U moet nieuwe Book-objecten maken en deze vervolgens in een nieuwe Listplaatsen:

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

Update: iets eenvoudiger… List<T>heeft een methode genaamd ConvertAlldie een nieuwe lijst retourneert:

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

Antwoord 2, autoriteit 33%

Maak een generieke ICloneable<T>-interface die u implementeert in uw Book-klasse, zodat de klasse weet hoe ze een kopie van zichzelf moet maken.

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

Je kunt dan de linq- of ConvertAll-methoden gebruiken die Mark noemde.

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

of

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

Antwoord 3, autoriteit 17%

Ik ben teleurgesteld dat Microsoft geen nette, snelle en gemakkelijke oplossing heeft aangeboden zoals Ruby doet met de Clone()-methode.

Behalve dat geeneen diepe kopie maakt, maakt het een ondiepe kopie.

Bij deep copying moet je altijd goed opletten wat je precies wilt kopiëren. Enkele voorbeelden van mogelijke problemen zijn:

  1. Cyclus in de objectgrafiek. Bookheeft bijvoorbeeld een Authoren Authorheeft een lijst van zijn Booken.
  2. Verwijzing naar een extern object. Een object kan bijvoorbeeld open Streambevatten die naar een bestand schrijft.
  3. Evenementen. Als een object een gebeurtenis bevat, zou vrijwel iedereen erop kunnen worden geabonneerd. Dit kan vooral problematisch worden als de abonnee zoiets als een GUI Windowis.

Er zijn in principe twee manieren om iets te klonen:

  1. Implementeer een Clone()methode in elke klasse die gekloond moet worden. (Er is ook een ICloneable-interface, maar die moet u nietgebruiken; een aangepaste ICloneable<T>-interface gebruiken, zoals Trevor voorstelde, is oké.) Als je weet dat je alleen een oppervlakkige kopie van elk veld van deze klasse hoeft te maken, kun je MemberwiseClone()om het te implementeren. Als alternatief kunt u een “copy-constructor” maken: public Book(Book original).
  2. Gebruik serialisatie om uw objecten te serialiseren in een MemoryStreamen deserialiseer ze vervolgens weer. Dit vereist dat je elke klasse markeert als [Serializable]en het kan ook worden geconfigureerd wat precies (en hoe) moet worden geserialiseerd. Maar dit is meer een “quick and dirty” oplossing, en zal hoogstwaarschijnlijk ook minder performant zijn.

Antwoord 4, autoriteit 8%

Nou,

Als u alle betrokken klassen markeert als serialiseerbaar, kunt u:

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

Bron:

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


Antwoord 5, autoriteit 7%

U kunt dit gebruiken:

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

Antwoord 6, autoriteit 6%

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

Dat zou precies moeten doen wat je wilt. Hier gedemonstreerd.


Antwoord 7, autoriteit 3%

C# 9 recordsen met uitdrukkingenkunnen maak het een beetje makkelijker, vooral als je type veel eigenschappen heeft.

Je kunt zoiets gebruiken als:

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

Ik deed dit als voorbeeld:

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

En de uitvoer was: (Wijzigingen aan objecten in Books2 hadden geen invloed op originele objecten in Books1)

Boeken1 bevat:

Boek { Naam = Boek1.1 }

Boek {Naam = Boek1.2 }

Boek { Naam = Boek1.3 }

Books2 bevat:

Boek { Naam = Gewijzigd }

Boek { Naam = Gewijzigd }

Boek { Naam = Boek1.3 }


Antwoord 8, autoriteit 2%

Aangezien Cloneeen objectinstantie van Boek zou retourneren, zou dat object eerst naar een Boek moeten worden gecast voordat u ToListerop kunt aanroepen. Het bovenstaande voorbeeld moet worden geschreven als:

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

Antwoord 9, autoriteit 2%

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

Antwoord 10

Als de klasse Array aan uw behoeften voldoet, kunt u ook de methode List.ToArray gebruiken, die elementen naar een nieuwe array kopieert.

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


Antwoord 11

Eenvoudige eenvoudige manier om een ​​generieke lijst te kopiëren:

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

Other episodes