Wie kopiere ich eine List<T> bei Value und nicht bei Reference in C# (wie lege ich eine unabhängige Kopie der Liste an)?

  • C#
  • .NET (FX) 4.5–4.8

Es gibt 25 Antworten in diesem Thema. Der letzte Beitrag () ist von RodFromGermany.

    Wie kopiere ich eine List<T> bei Value und nicht bei Reference in C# (wie lege ich eine unabhängige Kopie der Liste an)?

    Moin,

    ich scheitere gerade daran eine Liste mit komplexen Items also keine List<string> sondern List<T> bei Value zu kopieren. Ich möchte eine Liste verändern und später falls gewünscht diese Änderung rückgängig machen, in dem ich einfach diese BackupListe dann wieder zurück kopiere.

    Das scheitert daran, dass in C# List<T> bei Reference übergeben werden. Also eine Änderung in der original Liste immer auch in der Backupkopie "ist".

    Ist sowas nicht vorgesehen? Löst man so etwas anders ? Dann wie sollte ich dieses Undo Funktionalität am besten machen?

    Ich hab schon einiges Probiert z.B.:

    ListBackup.AddRange(ListOrig.ToArray());
    immer noch hat eine Änderung im Orginal Einfluss auf die Kopie
    --------------------------------------------------------------------------------------
    Mein konkretes Problem hab ich gelöst. Trotzdem würde ich gerne wissen wie man List<T> bei Value kopiert?
    Meine Lösung im konkreten Fall war:
    List<T> wobei T ein Property string hat, das ich per Backup sichern wollte. Diese Strings hab ich per for-Loop in eine List<string> gesichert und später nur diese Property in List<T> zurück kopiert auch mit for-Loop.
    codewars.com Rank: 4 kyu

    Dieser Beitrag wurde bereits 2 mal editiert, zuletzt von „nogood“ ()

    @nogood Dann musst Du eine Deep Copy der Liste erstellen:
    Spoiler anzeigen

    C#-Quellcode

    1. using System;
    2. using System.IO;
    3. using System.Runtime.Serialization;
    4. using System.Runtime.Serialization.Formatters.Binary;
    5. namespace WindowsFormsApplication1
    6. {
    7. /// <summary>
    8. /// Provides a method for performing a deep copy of an object.
    9. /// Binary Serialization is used to perform the copy.
    10. /// </summary>
    11. public static class DeepCopy
    12. {
    13. /// <summary>
    14. /// Perform a deep Copy of an object.
    15. /// </summary>
    16. /// <typeparam name="T">The type of object being copied.</typeparam>
    17. /// <param name="source">The object instance to copy.</param>
    18. /// <returns>The copied object.</returns>
    19. public static T Clone<T>(T source)
    20. {
    21. if (!typeof(T).IsSerializable)
    22. {
    23. throw new ArgumentException("The type must be serializable.", "source");
    24. }
    25. // Don't serialize a null object, simply return the default for that object
    26. if (object.ReferenceEquals(source, null))
    27. {
    28. return default(T);
    29. }
    30. using (MemoryStream stream = new MemoryStream())
    31. {
    32. IFormatter formatter = new BinaryFormatter();
    33. formatter.Serialize(stream, source);
    34. stream.Seek(0, SeekOrigin.Begin);
    35. return (T)formatter.Deserialize(stream);
    36. }
    37. }
    38. }
    39. }
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!
    @RodFromGermany Nicht dein Ernst... soviel Aufwand, um eine Kopie zu machen? Bedeutet das, dass man solche Problemstellungen anders lösen soll oder ist DeepCopy für so eine "UnDo" Sache immer noch die beste Lösung? Ich hatte schon mal eine ähnliche Frage und die Lösung war das mit mit Stack Push Pop etc. zu lösen.
    Könnte man das hier auch so machen List<T> Push to Stack -> List<T> ändern und noch mal auf Stack pushen oder ist das auch alles bei Ref.?
    codewars.com Rank: 4 kyu
    @Dksksm Ich bekomm es so jedenfalls nicht hin!? Ich hab das mit Stack Push Pop auch versucht ging leider auch nicht.

    Spoiler anzeigen

    C#-Quellcode

    1. ​using System;
    2. using System.Collections.Generic;
    3. using System.ComponentModel;
    4. using System.Data;
    5. using System.Drawing;
    6. using System.Linq;
    7. using System.Text;
    8. using System.Threading.Tasks;
    9. using System.Windows.Forms;
    10. namespace WF_Basic_StackPushPop
    11. {
    12. public partial class Form1 : Form
    13. {
    14. public List<MyModel> myModels = new List<MyModel> {
    15. new MyModel(0, "Walter"),
    16. new MyModel(1, "Hans"),
    17. new MyModel(2, "Uwe"),
    18. };
    19. //Stack<List<MyModel>> myStack = new Stack<List<MyModel>>();
    20. List<MyModel> Backup = new List<MyModel>();
    21. public Form1()
    22. {
    23. InitializeComponent();
    24. DgvShow.DataSource = myModels;
    25. }
    26. private void BtnAdd_Click(object sender, EventArgs e)
    27. {
    28. //myStack.Push(myModels);
    29. List<MyModel> Backup = myModels.ToList();
    30. myModels.Add(new MyModel(myModels.Count,TxbxAdd.Text));
    31. DgvShow.DataSource = null;
    32. DgvShow.DataSource = myModels;
    33. DgvShow.Refresh();
    34. }
    35. private void BtnRedo_Click(object sender, EventArgs e)
    36. {
    37. //myModels = myStack.Pop();
    38. myModels.Clear();
    39. myModels = Backup.ToList();
    40. DgvShow.DataSource = null;
    41. DgvShow.DataSource = myModels;
    42. DgvShow.Refresh();
    43. }
    44. }
    45. }
    codewars.com Rank: 4 kyu
    Habe hier noch eine Aussage gefunden, die ich nicht kannte: stackoverflow.com/questions/27…does-it-create-a-new-list


    One way of phrasing it, is that .ToList() makes a shallow
    copy. The references are copied, but the new references still point to
    the same instances as the original references point to. When you think
    of it, ToList cannot create any new MyObject() when MyObject is a class type.


    Dann geht das leider nicht, sorry.

    Nachtrag: Hier gibts auch noch Anregungen zu Clonecopy: stackoverflow.com/questions/22…m_campaign=google_rich_qa

    nogood schrieb:

    soviel Aufwand
    Ich hatte dieses Snippet einfach vorrätig.
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!
    Objekte kopieren ist ne üble Sache. Vor allem wenn diese mit Events arbeiten und in Beziehung zu anderen Objekten stehen.
    "Gib einem Mann einen Fisch und du ernährst ihn für einen Tag. Lehre einen Mann zu fischen und du ernährst ihn für sein Leben."

    Wie debugge ich richtig? => Debuggen, Fehler finden und beseitigen
    Wie man VisualStudio nutzt? => VisualStudio richtig nutzen
    Ich hätte da noch was gefunden, in meinem simple Beispiel sieht es so aus als ob das funktioniert.

    Spoiler anzeigen

    C#-Quellcode

    1. using System;
    2. using System.Collections.Generic;
    3. using System.ComponentModel;
    4. using System.Data;
    5. using System.Drawing;
    6. using System.Linq;
    7. using System.Text;
    8. using System.Threading.Tasks;
    9. using System.Windows.Forms;
    10. namespace WF_Basic_StackPushPop
    11. {
    12. public partial class Form1 : Form
    13. {
    14. public List<MyModel> myModels = new List<MyModel> {
    15. new MyModel(0, "Walter"),
    16. new MyModel(1, "Hans"),
    17. new MyModel(2, "Uwe"),
    18. };
    19. //Stack<List<MyModel>> myStack = new Stack<List<MyModel>>();
    20. List<MyModel> Backup = new List<MyModel>();
    21. public Form1()
    22. {
    23. InitializeComponent();
    24. DgvShow.DataSource = myModels;
    25. }
    26. private void BtnAdd_Click(object sender, EventArgs e)
    27. {
    28. //myStack.Push(myModels);
    29. Backup = myModels.ConvertAll(myModelProperties => new MyModel(myModelProperties.Id, myModelProperties.Name));
    30. myModels.Add(new MyModel(myModels.Count,TxbxAdd.Text));
    31. DgvShow.DataSource = null;
    32. DgvShow.DataSource = myModels;
    33. DgvShow.Refresh();
    34. }
    35. private void BtnRedo_Click(object sender, EventArgs e)
    36. {
    37. //myModels = myStack.Pop();
    38. myModels.Clear();
    39. myModels = Backup.ConvertAll(myModelProperties => new MyModel(myModelProperties.Id, myModelProperties.Name));
    40. DgvShow.DataSource = null;
    41. DgvShow.DataSource = myModels;
    42. DgvShow.Refresh();
    43. }
    44. }
    45. }


    Ich benutze ​LstBackup = LstmyModels.ConvertAll(myModelProperties => new MyModel(myModelProperties.Id, myModelProperties.Name)); aus diesem Post. Und Konvertiere zum selben Typ! Code-Ästhetisch würde mir das am besten gefallen. Oder gibt es Gründe das so nicht zu machen?
    codewars.com Rank: 4 kyu
    Bei sehr komplexen Objekten, könnte auch noch eine Serialisierung und Deserialisierung in Frage kommen.
    Die Binary-Serialisierung ist sehr schnell und übernimmt auch tiefe komplexere Objektaufbauten (Klassenaufbauten), wobei natürlich nur die Daten übernommen werden.

    Vorteil: Es kann gesteuert werden, was serialisiert werden soll. Sind irgendwelche Daten vorhanden, die nicht drin sein sollen, können die entsprechend mit "NoSerialize" (Attribut der Deklration) kennzeichnet werden.

    Freundliche Grüsse

    exc-jdbi

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „exc-jdbi“ ()

    @ErfinderDesRades "...soviel Aufwand..." bezog sich nicht auf die Lösung von RodFromGermany im speziellen sondern war eher der Frust darüber, dass es anscheinend nicht so etwas triviales gibt wie:

    List<T> ListBackup = ListOrig.ToList(); oder Copy.ByValue oder eben einen anderen Befehl der so kurz ist.

    Hatte ich nicht erwartet, dass das Thema "mach eine unabhängige Kopie von einem Objekt" soviel Aufwand erfordert.

    Jetzt wo ich akzeptiert hab, dass es so ist wie es ist ist die LSG von RodFromGermany wohl die bessere Lösung aus deinen genannten Gründen. Danke für den Denkanstoß.

    P.S. :
    Ich hab gerade noch was gelesen docs.microsoft.com/en-us/dotne…yformatter-security-guide Da wird gesagt das:
    "...
    The BinaryFormatter type is dangerous and is not recommended for data processing. Applications should stop using BinaryFormatter as soon as possible, even if they believe the data they're processing to be trustworthy. BinaryFormatter is insecure and can't be made secure.
    ..."

    Was soll ich davon halten?

    @RodFromGermany Ja dein Code funktioniert! (MyModel.Class musste ich noch das Attribut adden :) -> [Serializable]))
    codewars.com Rank: 4 kyu

    Dieser Beitrag wurde bereits 4 mal editiert, zuletzt von „nogood“ ()

    nogood schrieb:

    soviel Aufwand
    ein ca. 10-Zeiler für eine allgemeine Lösung - eigentlich kein Aufwand, und bereits in Post #2. ;)
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!
    Ja, da stolpert man: Klonen ("tiefe Kopie") klingt wie was ganz einfaches - ist es aber überhaupt nicht.
    Letztendlich sind es ja immer Felder die kopiert werden müssen - im Falle Properties eben die BackingFields der Props.

    Aber es gibt Felder, die kann man nicht sinnvoll kopieren:
    Was willst du zB mit einem Event machen: "Tiefe Kopie" bis zuende durchgezogen würde bedeuten, du musst auch von allen Objekten, die das Event abonnieren einen Klon ziehen.

    Oder einfach TreeNode
    Jeder Treenode hat eine Property TreeView - soll der komplette Treeview mit-geklont werden?
    Der ist aber wiederum mit dem kompletten Form verbunden, und das wiederum wohl mit allen Forms und auch noch Teilen des Betriebssystems.

    Daher gibts keinen ganz allgemeinen Weg fürs Klonen.
    Wie RFG gezeigt hat, für serialisierbare Klassen gibts eine allgemeingültige Lösung.
    Welche aber auch nicht bis in unendliche Tiefen geht, sondern halt so weit, wie die Serialisierung für die jeweilige Klasse halt konfiguriert ist - etwa durch Setzung des <NonSerialized>-Attributs an bestimmte Felder/Properties.
    Am ende läuft es darauf hin, das „Klonen“ selbst zu programmieren. Sprich, jede Property manuell von A nach B schreiben. Das ganze wird mit Events extrem anfällig und je tiefer da mit verschachtelten Objekten gearbeitet wird. Hab das alles hinter mir. DeepClone, Serialisierung... am Ende hab ich es „manuell“ gemacht.
    "Gib einem Mann einen Fisch und du ernährst ihn für einen Tag. Lehre einen Mann zu fischen und du ernährst ihn für sein Leben."

    Wie debugge ich richtig? => Debuggen, Fehler finden und beseitigen
    Wie man VisualStudio nutzt? => VisualStudio richtig nutzen

    nogood schrieb:

    Was soll ich davon halten?


    Entspricht zwar nicht gerade dem Thema, aber hier die Antwort. Der Link von @nogood bezieht sich nicht auf die Sicherheit der Rekonstruktion serialisierter Daten, sondern auf die Vertraulichkeit von sensiblen Daten, die nicht als KlarText ersichtlich sein dürfen.

    Wer meint, eine Binary-Serialisierung sei eine Verkryptung, der hat sowieso schon verloren. Direkt serialisierte Daten über das Internet zu versenden, bietet in keiner weise Schutz.

    Serialisieren heisst, die strukturierte Daten eines Objektes in eine sequenzielle Form zu bringen. Durch Deserialisierung, werden die sequentiellen Daten (Datenstrom) wieder in eine strukturierte für das ursprüngliche Objekt verständliche Weise umgeschrieben.

    Um Daten für die Übertragung übers Internet oder für das geschütze Abspeichern auf der HDD zu schützen gibt es Verkryptungsprogramme, wie z.B. das AES. Eine Serialisierung ist definitiv keine Verkryptung. Ganz im Gegenteil, der Datenstrom ist wiederum strukturiert und mit gezielten Methoden wieder auflösbar.

    Freundliche Grüsse

    exc-jdbi
    @RodFromGermany und alle anderen eine kleine Info bezüglich BinaryFormatter:
    BinaryFormatter is insecure and can't be made secure. For more information, see the BinaryFormatter security guide.
    Ähm - in dem Artikel gehts darum, ob mit BinaryFormatter serialisierte Daten sogenannte thrusty-Borders überschreiten dürfen. Also iwie transportiert werden, wo sie durch Angreifer manipuliert werden.
    Das trifft ja auf einen BinaryFormatter, der in einen temporären MemoryStream schreibt und liest, nicht zu.