Entwickler-Ecke

WPF / Silverlight - ObservableCollection Item Clone


RobAll - Fr 26.04.19 22:46
Titel: ObservableCollection Item Clone
Guten Abend,

in meinem letzten Beitrag wurde ein Problem gelöst, allerdings das Nächste eröffnet..

Aufgabe: Kopieren eines ObservableCollection Item (Datensatz einer DataGrid Row soll kopiert werden)
Problem: Das neue Objekt referenziert auf dem alten. -> _StammdatenList.Add(Datensatz) funktioniert aber der Alte und Neue Datensatz stehen in Relation;

Letzter Lösungsansatz:

C#-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
foreach (var item in (System.Collections.IEnumerable)Datensatz)
{
    ICloneable cloneable = item as ICloneable;
    if (cloneable != null)
    {
        _StammdatenList.Add(cloneable.Clone());
    }
}

"cloneable.Clone" erzeugt die Fehlermeldung:
Zitat:
Schweregrad Code Beschreibung Projekt Datei Zeile Unterdrückungszustand
Fehler CS1503 Argument "1": Konvertierung von "object" in "C.ViewModel.StammdatenViewModel" nicht möglich.

Nun komme ich nicht mehr weiter...

Bitte um Hilfe

Moderiert von user profile iconTh69: C#-Tags hinzugefügt
Moderiert von user profile iconTh69: Quote-Tags hinzugefügt


Ralf Jansen - Sa 27.04.19 00:08

Schau dir die Clone() Methode, sieh als was der Klon da zurückgegeben wird, überlege welchen Typ _StammdatenList.Add eigentlich erwartet und mach dann das passende.

Moderiert von user profile iconTh69: C#-Tags hinzugefügt


RobAll - Sa 27.04.19 12:37

also die _StammdatenList ist vom Typ ObservableCollection, und das Objekt cloneable.Clone vom Typ ICloneable.

Jetzt muss ich cloneable.Clone in ObservableCollection konvertieren?

Ich habe schon getestet aber ich bekomme immer die Info.

"cloneable.Clone"
Zitat:
Fehler CS1503 Argument "1": Konvertierung von "object" in "C.ViewModel.StammdatenViewModel" nicht möglich.


Bitte nochmals um Hilfe, sitze schon den ganzen Vormittag..


C#-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
foreach (StammdatenViewModel item in (System.Collections.IEnumerable)_StammdatenList)
{
    if (item.ID == 1)
    {
        ICloneable cloneable = item as ICloneable;
                       
        _StammdatenList.Add(cloneable.Clone());
}


Moderiert von user profile iconTh69: C#-Tags hinzugefügt


Ralf Jansen - Sa 27.04.19 12:57

Zitat:
also die _StammdatenList ist vom Typ Observable Collection, und das Objekt cloneable.Clone vom Typ ICloneable.

Nein. Die clonable Variable is vom Typ ICloneable. Die Instanz auf die von dieser Variablen verwiesen wird wird irgendwas anders sein. cloneable.Clone() liefert dir einen Klon. Und wenn du dir das ICloneable Interface genauer ansehen würdest würdest du sehen das der Return Typ der Methode object ist. Der Compiler kann es also nicht besser wissen als das was die Definition der Interface Methode hergibt und wird von object aussgehen.

Zitat:
Fehler CS1503 Argument "1": Konvertierung von "object" in "C.ViewModel.StammdatenViewModel" nicht möglich.

Woher object kommt habe ich gerade erklärt. Deine StammdatenList ObservableCollection verwaltet scheinbar Instanzen vom Typ StammdatenViewModel. Die Add Methode wird also als Parameter nur Verweise des Typs StammdatenViewModel (oder Ableitungen davon) annehmen. object ist kein StammdatenViewModel also wird der Kompiler bei dem Add Aufruf streiken.

C# ist eine Typsichersprache und will Typkompatibilität schon zur Compilezeit sicherstellen und das kann es nicht für deinen Code. Wenn du es besser weißt, also du weißt was für ein Typ aus der Clone Methode rauskommt dann caste [https://docs.microsoft.com/de-de/dotnet/csharp/programming-guide/types/casting-and-type-conversions] das Ergebnis der Methode auf diesen Typ (StammdatenViewModel) und übergebe das an Add.


RobAll - Sa 27.04.19 15:54

Danke für die Info, aber ich bekomme keinen Lösungsansatz..


Th69 - Sa 27.04.19 16:57

Von welchem Typ soll denn überhaupt Datensatz sein? StammdatenViewModel?
Warum iterierst du dann darüber?

Möchtest du nicht einfach davon eine Kopie erstellen?
Also in etwa:

C#-Quelltext
1:
2:
3:
var newItem = Datensatz.Clone() as StammdatenViewModel;
if (newItem != null)
    _StammdatenList.Add(newItem);


RobAll - Sa 27.04.19 20:41

Hallo, das habe ich auch schon getestet,
allerdings fehlt mir die using-Direktive, ich konnte hierzu nichts finden

C#-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Windows;
using System.Windows.Data;
using System.Windows.Input;
using System.Data;

Zitat:
Fehler CS1061 "StammdatenViewModel" enthält keine Definition für "Clone", und es konnte keine Clone-Erweiterungsmethode gefunden werden, die ein erstes Argument vom Typ "StammdatenViewModel" akzeptiert (möglicherweise fehlt eine using-Direktive oder ein Assemblyverweis).

-> Mit dem ICloneable habe ich mich aus dem Netz verstrickt, als Anfänger verläuft man sich sehr schnell auf verschiedenen Wegen..

Moderiert von user profile iconTh69: C#-Tags hinzugefügt
Moderiert von user profile iconTh69: Quote-Tags hinzugefügt


Th69 - So 28.04.19 09:43

Die Clone-Methode (sowie das Hinzufügen des ICloneable-Interface) mußt du natürlich selber bei der Klasse implementieren.

Ich finde diese nicht-generische Schnittstelle sowie keine gute Lösung (eben wegen dem zusätzlichen Cast).

Du könntest auch einfach einen Konstruktor für das Kopieren bereitstellen:

C#-Quelltext
1:
2:
3:
4:
public StammdatenViewModel(StammdatenViewModel other)
{
  // copy elements
}

Und dann einfach per new kopieren:

C#-Quelltext
1:
var newItem = new StammdatenViewModel(Datensatz);                    


Aber eigentlich solltest du doch ein darunterliegendes Modell haben, das kopiert wird und daraus dann ein neues VM-Objekt erzeugen.


Delete - So 28.04.19 15:13

- Nachträglich durch die Entwickler-Ecke gelöscht -


Christian S. - So 28.04.19 23:30

@Frühlingsrolle: Das verfehlt irgendwie das, was man mit dem klonen eines Objektes erreichen will, nämlich ein Kopie mit unterschiedlicher Referenz zu erzeugen. Original und Klon sind bei Dir das selbe (und nicht nur das gleiche) Objekt, dann kann ich auch einfach eine Zuweisung machen.


Delete - Mo 29.04.19 00:48

- Nachträglich durch die Entwickler-Ecke gelöscht -


Th69 - Mo 29.04.19 09:13

Sorry, aber so auch nicht:
- unnotiger Member _clone
- (dadurch auch) IDisposable unnötig

Außerdem ist das Beispiel mit einem string-Member schlecht gewählt, da dieser zwar eine Klasse (Referenz) ist, jedoch immutable!


Delete - Mo 29.04.19 09:29

- Nachträglich durch die Entwickler-Ecke gelöscht -


RobAll - Mo 29.04.19 21:53

Da gibt es nun viele Informationen von euch.

Welchen Weg soll ich denn nun als Anfänger verwenden, um das Objekt ohne Referenz zu kopieren/klonen?

Beispiel von Th69, hier benötige ich nun noch die Klasse Methode für Clone

C#-Quelltext
1:
2:
3:
var newItem = Datensatz.Clone() as StammdatenViewModel;
if (newItem != null)
    _StammdatenList.Add(newItem);


Moderiert von user profile iconTh69: C#-Tags hinzugefügt


Th69 - Di 30.04.19 08:28

Das mußt du dann individuell ausprogrammieren (d.h. für alle Member einzeln) - Stichwort: Deep Copy

Aber besser als die Clone-Methode wäre es so, wie in meinem vorletzten Beitrag beschrieben!


RobAll - Mi 01.05.19 01:16

Sorry, ich komme immer noch nicht weiter..
So viel gelesen, getestet.... seid dem 26.04
Ich will einen Datensatz im DataGrid kopieren und diesen danach ändern

C#-Quelltext
1:
_StammdatenList.Add(Datensatz); // funktioniert, allerdings mit Relation                    

Mein letzter Versuch.., mit euren anderen Informationen bin ich leider nicht klar gekommen.

C#-Quelltext
1:
2:
StammdatenViewModel a = Datensatz , b ;
b = new StammdatenViewModel (a);

Zitat:
(a);
Fehler CS1503 Argument "1": Konvertierung von "C.ViewModel.StammdatenViewModel" in "C.Stammdaten" nicht möglich. ???

Bitte nochmals um Hilfe und um ein CodeBeispiel, bin Anfänger/Quereinsteiger

Danke Euch vorab nochmals!!

Moderiert von user profile iconTh69: C#-Tags hinzugefügt
Moderiert von user profile iconTh69: Quote-Tags hinzugefügt


Th69 - Mi 01.05.19 09:04

Das ist doch jetzt schon das 2. mal, daß du diesen Fehler (CS1503 [https://docs.microsoft.com/de-de/dotnet/csharp/misc/cs1503]) erhältst.
Du scheinst Probleme mit dem Verständnis von Datentypen zu haben.

In deinem Fall hast du wohl einen Konstruktor

C#-Quelltext
1:
public StammdatenViewModel(Stammdaten stammdaten)                    

aber eben keinen (so wie ich oben schon geschrieben habe):

C#-Quelltext
1:
public StammdatenViewModel(StammdatenViewModel stammdatenViewModel) // oder other (wie bei mir)                    

Also mußt du entweder diesen ausimplementieren oder aber (so wie ich auch schon geschrieben habe) das Model (d.h. die Stammdaten) kopieren und dann die Kopie als Parameter an den Konstruktor übergeben.

Sorry, anders kann ich es dir nicht erklären. Vllt. solltest du noch mal ein Buch zu Datentypen und deren Benutzung sowie Umwandlung dazu lesen.

PS: Und bitte beim Posten demnächst selber die passenden Tags (C#, Quote etc.) benutzen.


RobAll - Do 02.05.19 23:35

Leider referenzieren das kopierte und original Objekt im DataGrid immer noch. (Kopie und Original werden beim Editieren geändert)
Code:

C#-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
//Konstruktoraufruf mit Parameterübergabe
StammdatenViewModel Item = new StammdatenViewModel(Datensatz);
_StammdatenList.Add(Item);

// Kopierkonstruktor
public StammdatenViewModel(StammdatenViewModel Item)
{
    Einzelteilname = Item.Einzelteilname;
    Einzelteilnummer = Item.Einzelteilnummer;
    Einzelteilrevision = Item.Einzelteilrevision;
}


Moderiert von user profile iconTh69: C#-Tags hinzugefügt


Chiyoko - Fr 03.05.19 09:05

Hallo,

nutze bitte C# Tags für deinen Code. ( [ cs ] )

Um eine Instanz zu kopieren, gibt es mehrere Wege.
Zuerst mal gilt es zu unterscheiden, ob du Referenztypen oder Wertetyen in deiner Klasse hast.

Generell sind alle Klassen, abgesehen von einigen Ausnahmen (z.b. String), Referenztypen.
Wertetypen sind Structs, int, long, short usw.

Stell dir einen Bereich im Speicher vor, wo deine Klasse abgelegt wurde.
Erstellst du von dem Objekt eine neue Instanz, wird nur ein Pointer erstellt, der auf die Referenz verweist.
Das ist die sogenannte flache bzw. Schattenkopie. Du hast also ein weiteres Objekt erstellt, wo die
Instanzen in der Klasse immer auf das selbe Original verweisen.

Andernfalls brauchst du eine tiefe Kopie bzw. Deep Copy.
Von letzterem gibt es verschiedene Möglichkeiten.
Wenn du nur eine Klasse vergleichen willst, implementiere Equals, dann kann das Objekt auch Schattenkopie sein.

Ansonsten kannst du das Objekt in der Klasse serialisieren oder jede Property in der Klasse neu setzen.
Das geht am einfachsten mit einer Erweiterung.



C#-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
52:
53:
54:
55:
56:
//Serialisierung (Klasse benötigt das Attribut Serializeable
public static T DeepClone<T>(T obj)
{
 using (var ms = new MemoryStream())
 {
   var formatter = new BinaryFormatter();
   formatter.Serialize(ms, obj);
   ms.Position = 0;

   return (T) formatter.Deserialize(ms);
 }
}

[Serializable]
public class SecondClass
{
    public int Id { get; }

    public string Quality { get; set; }

    public int HitQuality { get; set; }


    public override bool Equals(object obj)
    {
        var other = obj as SecondClass;
        if (Id == other?.Id) return true;

        return false;
    }

    public SecondClass(int hit, string qualy)
    {
        Id ++;
        Quality = qualy;
        HitQuality = hit;
    }
}

public class TestClass
{
    public string Quality { get; set; }

    public int HitQuality { get; set; }

    public List<SecondClass> ScList { get; } = new List<SecondClass>();

    public TestClass()
    {
        ScList.Add(new SecondClass(1"Test1"));
        ScList.Add(new SecondClass(2"Test2"));

        SecondClass s = ScList.FirstOrDefault();
        var deepCopy = DeepClone(s);
    }
}


Th69 - Fr 03.05.19 09:25

@Chiyoko: Ich kann nicht erkennen, daß dein Code eine "Deep Copy" durchführt (auch deine Methode setzt nur die einzelnen Properties aus dem Original, d.h. führt nur eine "Shallow Copy" durch... - deine Klasse SecondClass ist aber auch ungeeignet, das zu demonstrieren).
Bei einer "Deep Copy" muß für jedes Referenztyp-Element ein neues Objekt (inklusive Unterobjekte) angelegt werden (d.h. getrennten Speicherbereich) - aber mit dem Stichwort hätte @RobAll ja auch schon lange im Internet suchen können.

PS: Auch String ist eine Klasse, d.h. ein Referenztyp, nur eben "immutable" implementiert!


Chiyoko - Fr 03.05.19 09:54

DeepCopy erzeugt dir eine Instanz einer Klasse und deren Referenzen. Das ist korrekt und bereits getestet, sonst würde ichs nicht schreiben.
Vielleicht ist das Beispiel nur schlecht gewählt.
In diesem Fall wäre die Serialisierung des Objekts wohl geeigneter.

String braucht kein DeepCopy, weswegen ich das als "Ausnahme" ansehe.

Ich war ja selbst mal Anfänger und kenne das Problem. Im Internet wirst du manchmal erschlagen und findest aus diesem Grund keinen eindeutigen Weg.

EDIT:
Ich habe mein Beispiel angepasst. Das hat jetzt nur 2 Nachteile. Einerseits muss die Klasse serialisierbar sein und zum anderen kann es zu Problemen mit abgeleiteten Klassen kommen.

Edit2:
Sorry, du hast Recht. Mein Fehler. Die Deepcopy Funktion erzeugt nur eine Schattenkopie.

Edit3:
Der vollständigkeithalber hier [ https://docs.microsoft.com/de-de/dotnet/api/system.object.memberwiseclone?view=netframework-4.8] ein Beispiel von Microsoft, was die Implementierung von Clone (ohne Interface) zeigt, wie bereits von Th69 vorgeschlagen wurde. Da ist das Beispiel auch besser als meins. :D


Ralf Jansen - Fr 03.05.19 14:41

Zitat:
Leider referenzieren das kopierte und original Objekt im DataGrid immer noch. (Kopie und Original werden beim Editieren geändert)

Dein Konstruktor erstellt eine Kopie. Wenn sich dein anderer Code so verhält als wäre Original und Kopie die gleiche Instanz dann hast du in diesem Code einen Fehler aber nicht in dem hier gezeigten.

Zitat:
@Chiyoko: Ich kann nicht erkennen, daß dein Code eine "Deep Copy" durchführt

Der BinaryFormatter kopiert auch Referenztypen mit nicht nur die Referenzen darauf. Für mich ist das eine deep copy. Das geht sogar soweit das wenn man Events/Delegaten verdrahtet hat die Objekte am Ende des Events/Delegaten ebenfalls mit serialisiert werden.

Das ganze "wie man etwas kopieren könnte" hilft uns (oder besser RobAll) aber überhaupt nicht weiter. Wir wissen gar nicht was für eine Art von Kopie er braucht. Meist braucht man eh etwas das irgendwo dazwischen liegt. Also weder eine flache noch eine tiefe Kopie.

Zitat:
Edit2:
Sorry, du hast Recht. Mein Fehler. Die Deepcopy Funktion erzeugt nur eine Schattenkopie.

Ich verstehe flache oder tiefe Kopie bzw. shallow or deep copy. Aber was ist in dem Kontext eine Schattenkopie :gruebel:


Chiyoko - Fr 03.05.19 14:53

user profile iconRalf Jansen hat folgendes geschrieben Zum zitierten Posting springen:

Zitat:
@Chiyoko: Ich kann nicht erkennen, daß dein Code eine "Deep Copy" durchführt

Der BinaryFormatter kopiert auch Referenztypen mit nicht nur die Referenzen darauf. Für mich ist das eine deep copy. Das geht sogar soweit das wenn man Events/Delegaten verdrahtet hat die Objekte am Ende des Events/Delegaten ebenfalls mit serialisiert werden.

Zitat:
Edit2:
Sorry, du hast Recht. Mein Fehler. Die Deepcopy Funktion erzeugt nur eine Schattenkopie.

Ich verstehe flache oder tiefe Kopie bzw. shallow or deep copy. Aber was ist in dem Kontext eine Schattenkopie :gruebel:


Nein, ich hatte da vorher 2 verschiedene Varianten, um eine Kopie einer Instanz zu erstellen.
Eine davon war falsch.

Ja, das heisst Shallow Copy. Aus Gewohnheit schreib ich aber seit jeher "Shadow/Schatten" :D