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 Book
objecttype 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_2
zijn 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_2
en vervolgens diezelfde elementen van book_1
te controleren in een TextBox
.
books_1[0].title and books_2[1].title
zijn 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_1
en books_2
volledig 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 Book
complexer 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 List
plaatsen:
List<Book> books_2 = books_1.Select(book => new Book(book.title)).ToList();
Update: iets eenvoudiger… List<T>
heeft een methode genaamd ConvertAll
die 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:
- Cyclus in de objectgrafiek.
Book
heeft bijvoorbeeld eenAuthor
enAuthor
heeft een lijst van zijnBook
en. - Verwijzing naar een extern object. Een object kan bijvoorbeeld open
Stream
bevatten die naar een bestand schrijft. - 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
Window
is.
Er zijn in principe twee manieren om iets te klonen:
- Implementeer een
Clone()
methode in elke klasse die gekloond moet worden. (Er is ook eenICloneable
-interface, maar die moet u nietgebruiken; een aangepasteICloneable<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 jeMemberwiseClone()
om het te implementeren. Als alternatief kunt u een “copy-constructor” maken:public Book(Book original)
. - Gebruik serialisatie om uw objecten te serialiseren in een
MemoryStream
en 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:
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 Clone
een objectinstantie van Boek zou retourneren, zou dat object eerst naar een Boek moeten worden gecast voordat u ToList
erop 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