Laden...

C#: Verweis- oder Werttyp - wie erkennen und implementieren?

Letzter Beitrag vor 16 Jahren 19 Posts 3.002 Views
C#: Verweis- oder Werttyp - wie erkennen und implementieren?

Hallo,

ich stolper gerade bei der Erstellung einer Klasse über die Frage, ob ich da jetzt vom Grundsatz her eine "Verweis"- oder eine "Wert"-Klasse fabriziere(n soll). Hab hier im Forum bereits gesucht.

Wenn ich das richtig verstanden habe, gelten die allgemeinen oder "primitiven" Typen wie int, byte, usw. als Werttyp. Alles was komplexer anzusehen ist (= eigene Klasse) ist dann wohl ein Verweistyp. Kann man das so sehen, oder muss ich den Maßstab etwas präziser ansetzen?

Die allgemeine Frage wäre also, wie erkenne ich richtig, welcher Art meine Klasse sein soll?

Um es dann zu konkretisieren (ein Beispiel ist immer gut 🙂), ich bastle an einer Klasse, welche einen Buffer darstellt. Diese Buffer-Klasse soll später in einer Mikrocontroller-Klasse verwendet werden (daher hielt ich es für sinnvoll, nicht einfach ein Array zu verwenden).
Das heisst, meine Buffer-Klasse ist dann ein Verweistyp. Jetzt komm ich ein bisschen ins Schleudern, was z.B. das kopieren von Objekten der Buffer-Klasse angeht.

Wenn ich schreibe:


Memory MemA = new Memory();
Memory MemB = new Memory();

MemB = MemA;

erreiche ich damit ja nur, dass MemB auf MemA verweist, richtig?
Wie implementiere ich nun das Kopieren als Werttyp? Soll ich eine "Copy"-Methode erstellen, bei der ein Verweis auf das zu kopierende Objekt gemacht wird und dessen Werte kopiert in ein neues Objekt kopiert werden?
Wie macht man sowas richtig? Bin auch schon auf IClonable usw. gestoßen, aber es scheint leider mehrere Ansätze zu geben 🙁 --> Verwirrung

Vielen Dank für eure Hilfe.

Locutus

====================================
1001011010101010101101110101111000101010101010
Ich assimilier dich...
Und dich auch...
Ich mein's ernst!

Der wohl bedeutenste Unterschied zwischen Verweis und Werttyp ist, dass der Werttyp auf dem Stack abgelegt wird und der Verweis(Zeiger) auf dem Heap.

Der Vorteil vom Werttyp ist, dass er auf dem Stack erstellt wird und beim Verlassen der Funktion (Scopes) automatisch aufgeräumt wird. Der Stack ist allerdings bei "normal" Windows-XP beispielsweise "nur" 1 MB groß

Der Vorteil vom Heap ist, dass er über den gesamten virtuellen Speicher reicht (Auslagerungsdatei sowie physischer Speicher). So ist es möglich mehrere Hundert MB im Speicher haben. Der Nachteil ist, der Speicher muss teilweise manuell erstellt und auch wieder manuell freigegeben werden sowie "defragmentiert" werden. Das Erstellen wird in C# über die Konstruktoren realisiert, das freigegben, automatisch (mit Verzögerung) vom Gac

Wenn du kleine Objekte hast, ist es besser und schneller diese auf dem Stack (Werttyp) anzulegen. Hast du Objekte, die größer sind, und auch eine lange Lebensdauer haben sollen, verwende Verweistyp.

Weiterhin ein großer Nachteil von Werttypen ist, sie werden bei jeder Zuweisung (an Funktion oder andere Variable) kopiert

Es gibt 3 Arten von Menschen, die die bis 3 zählen können und die, die es nicht können...

... Hast du Objekte, die größer sind, und auch eine lange Lebensdauer haben sollen, verwende Verweistyp.

Weiterhin ein großer Nachteil von Werttypen ist, sie werden bei jeder Zuweisung (an Funktion oder andere Variable) kopiert

Hm... Okay, dann ist derVerweistyp wohl doch das richtige. Ich hatte zuerst mit Werttyp geliebäugelt. Allerdings kann meine Memory-Klasse ein byte-Array von bis zu 16MB erzeugen.

Wie erreiche ich dann das Kopieren? Implementation der IClonable-Schnittstelle in einer eigenen Clone-Methode wäre dann wohl das richtige, oder?

Locutus

====================================
1001011010101010101101110101111000101010101010
Ich assimilier dich...
Und dich auch...
Ich mein's ernst!

Ein Array (Egal ob die Basis ein Verweis oder Werttyp) ist immer ein Verweistyp.

Ja, um kopieren bei Klassen (Verweistypen) zu implementieren empfiehlt es sich ICloneable zu verwenden.

Vllt. noch ein Tip:
Weiterhin gibt es noch Boxing(Unboxing), dort wird ein Werttyp in ein object gecastet. Da Objekt immer ein Verweistyp ist, landen die Daten auf dem Heap (Kopie). Beim Zurückcasten wird wiederrum ein Struct im Stack abgelegt (wieder als Kopie) und das Objekt auf dem Heap (bei Nichtmehrbenutzung) als "zu säubernd" markiert.

Es gibt 3 Arten von Menschen, die die bis 3 zählen können und die, die es nicht können...

Hallo kleines_eichhoernchen, hallo Locutus,

Der wohl bedeutenste Unterschied zwischen Verweis und Werttyp ist, dass der Werttyp auf dem Stack abgelegt wird und der Verweis(Zeiger) auf dem Heap.

das finde ich relativ unerheblichen Unterschied. Das wichtige ist doch, dass bei Werttypen bei Zuweisungen und Parameterübergabe der Wert übergeben (=kopiert) wird und bei Verweistypen "nur" ein Verweis auf das Objekt selbst übergeben (=kopiert) wird. Also das bei Werttypen Werte und bei Verweistypen Verweise übergeben/zugewiesen werden.

Wenn du kleine Objekte hast, ist es besser und schneller diese auf dem Stack (Werttyp) anzulegen. Hast du Objekte, die größer sind, und auch eine lange Lebensdauer haben sollen, verwende Verweistyp.

Das halte ich für eine sehr technische und sehr ungünstige Unterscheidung. Das Kriterium in meinen Augen ist, ob die "Objekte" "nur" Werte darstellen oder ob sie eine eigene Identität haben. Eine Zahl oder Datum ist z.B. nur ein Wert. Eine Person (ein Personen-Objekt) hat eine Identität. Es ist kein reiner Wert. Wenn es zwei Personen-Objekte mit den gleichen Namen gibt, so sind es doch zwei unterschiedliche Objekte (Personen). Wenn die gleiche Person gemeint wäre, würde es nur ein Personen-Objekt geben. Deshalb sollten auch solche Objekte nicht ICloneable implementieren.

Natürlich kann man die technischen Aspekte nicht völlig außer acht lassen, aber in erster Linie sollte man doch danach gehen, ob etwas ein Wert ist oder eine Identität hat.

Weiterhin ein großer Nachteil von Werttypen ist, sie werden bei jeder Zuweisung (an Funktion oder andere Variable) kopiert

Das ist übrigens auch der große Vorteil von Werttypen. 🙂

Implementation der IClonable-Schnittstelle in einer eigenen Clone-Methode wäre dann wohl das richtige, oder?

In deinem Fall hast du quasi einen Wert, also deinen Puffer, der aber als byte-Array nunmal kein Werttyp ist. Das ist genau das Gebiet, in dem ICloneable zum Tragen kommt.

herbivore

Weiterhin ein großer Nachteil von Werttypen ist, sie werden bei jeder Zuweisung (an Funktion oder andere Variable) kopiert
Das ist übrigens auch der große Vorteil von Werttypen. 🙂

Als großen Nachteil sehe ich leider im .NET die String-Klasse an. Ist zwar eine Klasse (Verweis-Typ), wird aber ständig bei jeder Operation und Zuweisung kopiert und der Speicherverbrauch je nach Anwendungsfall vervielfacht

Es gibt 3 Arten von Menschen, die die bis 3 zählen können und die, die es nicht können...

Als großen Nachteil sehe ich leider im .NET die String-Klasse an. Ist zwar eine Klasse (Verweis-Typ), wird aber ständig bei jeder Operation und Zuweisung kopiert und der Speicherverbrauch je nach Anwendungsfall vervielfacht

also bei zuweisungen wird nur die referenz kopiert, genauso wie bei parameter übergabe.
und wenn das mal wirklich stört, kann man immer noch die StringBuilder Klasse verwenden. Die beiden ergänzen sich sehr gut, und sind auch in Java nicht viel anders implementiert (ausgenommen der dort nicht überladbaren operatoren)

loop:
btst #6,$bfe001
bne.s loop
rts

Ja StringBuilder/MemoryStream oder direkt ein Char-Array sind eine gute alternative, leider finden sie keine Unterstützung in beispielsweise int.Parse, Regex, oder System.Data.

gehört aber nicht ganz zum Thema😉

Es gibt 3 Arten von Menschen, die die bis 3 zählen können und die, die es nicht können...

Hallo kleines_eichhoernchen,

doch in gewissem Sinne schon, sogar ziemlich eng, weil Strings Wert-Objekte sind. Also String ein Typ, der nach meinen konzeptionellen Gründen ein Werttyp sein würde, aber aus deinen technischen Gründen als Verweistyp realisiert ist. Die Konsequenz ist, dass man den Typ immutable implementiert, um so gewisse Unterschiede auszugleichen und die Verwendung praktikaber zu machen.

herbivore

Hallo zusammen,

erstmal vielen Dank für eure Beiträge.

@herbivore:

Du hast geschrieben, dass du "ICloneable" trotzdem nicht implementieren würdest.
Wie erreiche ich denn jetzt nun das Kopieren? Soll ich eine "normale" Clone-Methode schreiben?

Etwa so:


public Memory Clone() {
	Memory Mem = new Memory();

	Mem.bla = this.bla;
	Mem.blabla = this.blabla;
	Mem.blubb = this.blubb;
	//usw.

	return(Mem);
}

Oder geht das in die Hose? Was mir bei so einer Variante halt nicht gefällt, wäre dass ich jedesmal wenn ich die Klasse erweitere, auch die Clone-Methode nicht vergessen darf, sonst wird nicht vollständig kopiert.

Locutus

====================================
1001011010101010101101110101111000101010101010
Ich assimilier dich...
Und dich auch...
Ich mein's ernst!

ich glaub es gibt auf object eine protected Methode die MemberwiseClone() heisst. Die könnte das übernehmen. Wenn deine Klasse allerdings Referenzen enthält, wird die Referenz kopiert und nicht das Objekt auf das sie zeigt.

loop:
btst #6,$bfe001
bne.s loop
rts

@herbivore:

Du hast geschrieben, dass du "ICloneable" trotzdem nicht implementieren würdest.
Wie erreiche ich denn jetzt nun das Kopieren? Soll ich eine "normale" Clone-Methode schreiben?

das hast du falsch verstanden. er meinte glaube ich: wenn das objekt eine identität darstellen soll, dann soll man Iclonable NICHT implementieren. da es sich bei deinem bytearray vermutlich nciht um eine identität handelt, ist iclonable genau das richtige.

Hallo JAck30lena,

genauso war es gemeint.

Hallo Locutus,

Etwa so:

Nein, auf keinen Fall. Immer MemberwiseClone verwenden, um das Objekt zu erstellen!

Soll ich eine "normale" Clone-Methode schreiben?

Wenn ich schreibe, man solle ICloneable nicht implementieren, meine ich damit natürlich: Keine Clone Methode schreiben. Was sollte denn dadurch gewonnen sein, wenn man eine Clone-Methode schreibt, ohne das man ICloneable implementiert. Es geht doch darum, dass man Objekte mit Identität nicht klonen soll. Also eben wirklich nicht klonen. Also sollte es natürlich auch keine Clone-Methode geben. Solche Objekte soll es nur einmal geben und das ist auch gut so.

Aber wie gesagt, du hast ja hier kein Objekt mit Identität.

herbivore

Aber wie gesagt, du hast ja hier kein Objekt mit Identität.

Ja, das war eine meiner Kernfragen: Wann hab ich denn eins mit Identität? Warum soll meine Memory-Klasse keine Identität haben bzw. wie erkenne ich das richtig?

Locutus

====================================
1001011010101010101101110101111000101010101010
Ich assimilier dich...
Und dich auch...
Ich mein's ernst!

Vielleicht noch als generelle Anmerkung zu der Frage, wann man Strukturen statt Klassen nehmen sollte: 📗 http://www.guidetocsharp.de/Classes.aspx#Structures

Wissensvermittler und Technologieberater
für .NET, Codequalität und agile Methoden

www.goloroden.de
www.des-eisbaeren-blog.de

Hallo Locutus,

Ja, das war eine meiner Kernfragen: Wann hab ich denn eins mit Identität?

dann möchte ich meinen, eine der ungestellen Kernfragen.

Ob etwas ein Wert oder ein Ding mit Identität ist, ergibt sich eigentlich immer automatisch. Eine bestimmte Münze z.B. hat den Wert "1 Euro". Wenn man das abbilden würde, könnte man einen struct Money nehmen. Die Münze als Münze aber hat natürlich eine Identität. Eben genau diese eine Münze, keine andere. Wenn man das abbilden würde, könnte man eine class Coin nehmen.

herbivore

die frage ist:

möchtest du mal if(myobject == givenobject) verwenden oder ist es dir egal, da du die werte sowieso nur an ausgewählten stellen brauchst und das objekt danach auch zerstört wird.

Hallo JAck30lena,

es kann auch sinnvoll sein, Werte mit == zu vergleichen. Die Frage ist, ob der Vergleich zwischen zwei "Objekten" auf den Vergleich des Wertes oder den Vergleich der Identität hinausläuft.

Also das Vergleichen wollen ist nicht das Kriterium, sondern wie die Gleichheit sinnvollerweise definiert wäre.

herbivore

Hallo zusammen,

vielen Dank für die weiteren Beiträge.

@Herbivore:

Ja, das war eine meiner Kernfragen: Wann hab ich denn eins mit Identität?
dann möchte ich meinen, eine der ungestellen Kernfragen.

Okay, da hab ich mich dann wohl ungünstig ausgedrückt. Mit dem Satz:

Die allgemeine Frage wäre also, wie erkenne ich richtig, welcher Art meine Klasse sein soll?

und

Ja, das war eine meiner Kernfragen: Wann hab ich denn eins mit Identität? Warum soll meine Memory-Klasse keine Identität haben bzw. wie erkenne ich das richtig?

meinte ich, die Kernfrage so gestellt zu haben 🙂

Ob etwas ein Wert oder ein Ding mit Identität ist, ergibt sich eigentlich immer automatisch. Eine bestimmte Münze z.B. hat den Wert "1 Euro". Wenn man das abbilden würde, könnte man einen struct Money nehmen. Die Münze als Münze aber hat natürlich eine Identität. Eben genau diese eine Münze, keine andere. Wenn man das abbilden würde, könnte man eine class Coin nehmen.

Sehr gut beschrieben, danke. Ich will natürlich eine Memory-Klasse mit "Identität", damit ich die Memory-Buffer auch kopieren kann. Ich denke, ich werd das Kopieren dann wohl mit ICloneable realisieren.

Vielen Dank für eure Hilfe.

Locutus

====================================
1001011010101010101101110101111000101010101010
Ich assimilier dich...
Und dich auch...
Ich mein's ernst!