![]() |
Boxing und Unboxing in C#
Hmm ich möchte hier nochmal anknüpfen. Und zwar habe ich grad etwas über Boxing und Unboxing in C# gelesen. Wie es funktioniert habe ich verstanden. Eine Integer Variable (Wertetyp) kann zu einem Object (Referenztyp) "konvertiert" werden.
Das würde in C# so aussehen:
Delphi-Quellcode:
Jetzt habe ich aus einer Wertetyp Variable eine Referenztyp Variable gemacht. Aber warum ? Ich seh den Vorteil nicht, kann es jemand Anhand eines Beispiels erklären?
int i = 321;
object o = i; //boxed Dankeschön :thumb: |
AW: Heap und Stack - Unterschiede
Das Einzige, was ich mir da vorstellen kann, daß man unterschiedliche Wert-Typen in der Objektvariable unterbringen kann. :gruebel:
Und am Ende könnte man über Typprüfungen unterscheiden was für Werte enthalten sind. quasi Currency, Integer, Int64, Float oder sonstwas. Vielleicht auch, um einen Wert irgendwo unterzubringen, wo sonst nur ein Objekt reinpaßt? Also das Umgekehrte, wie man im Delphi alles Mögliche in einen Integer/Pointer reincastet. |
AW: Heap und Stack - Unterschiede
Wenn man eine Referenz auf einen Integer braucht und nicht den Integer selbst.
Pointer gibbet in C# ja so nicht (unsafe ausgenommen) (dein Integer "wandert" also vom Stack in den Heap) |
AW: Heap und Stack - Unterschiede
Ich bin neu in der .NET Programmierung aber ich arbeite hin und wieder mit Delphi. Eine Referenz auf eine Variable hab ich bisher nie gebraucht deswegen die Frage: Warum bzw. wann braucht man sowas? Beispiel ?
|
AW: Heap und Stack - Unterschiede
Delphi-Quellcode:
Wenn es darauf ankommt, dass j IMMER den selben Wert hat wie i, dann brauchst du einen Zeiger.
var
i, j: integer; i := 5; j := i; i := 10; // i = 10, j = 5
Delphi-Quellcode:
Hinweis: hier (Delphi-Beispiel) wandert nichts in den Heap!
var
i: integer; j: ^integer; i := 5; j := @i; i := 10; // i = 10, j^ = 10 |
AW: Heap und Stack - Unterschiede
Hmm aber mein Code funktioniert doch so:
i wird als Integer deklariert und mit einer Zahl initialisiert. (i liegt auf dem Stack) o wird als Objekt deklariert. Ein neues Objekt wird auf dem Heap erstellt und der Inhalt von i wird in das Objekt kopiert. o liegt auf dem Stack und verweist auf das Objekt. Da es sich bei o um eine Referenz auf ein Objekt, welches eine Kopie von i ist, handelt, wird sich bei Änderung von i, o nicht verändern. Jedenfalls habe ich das so verstanden ... :?:
Delphi-Quellcode:
int i = 321;
object o = i; //boxed |
AW: Heap und Stack - Unterschiede
Der Sinn (oder zumindest ein großer Vorteil) ist, dass du mittels boxing eine generische Klasse/Funktion schreiben kannst, die wirklich alle Typen unterstützt.
Einer Funktion, die einen Object Parameter erwartet, kann also wirklich alles übergeben werden. Und diese kann dann auch auf ihren Parameter .toString() aufrufen, da diese Funktion ja in object deklariert ist. Eine List<int> und eine List<double> benutzen also die gleiche List<T> wie List<String> oder List<StringBuilder> Genaueres können dir bestimmt Phoenix oder Elvis erzählen, die kennen sich noch besser mit .net aus :-) |
AW: Heap und Stack - Unterschiede
Zitat:
Zitat:
Delphi-Quellcode:
int i = 0;
string s = i.ToString(); Zitat:
|
AW: Heap und Stack - Unterschiede
Ich habe mir das mit dem Boxing nicht so heftig/blöd vorgestellt.
![]() Sowas sollte man doch verbieten, oder? |
AW: Heap und Stack - Unterschiede
Zitat:
Es geht auch kürzer:
Code:
(5).ToString()
|
AW: Heap und Stack - Unterschiede
Achso da wird quasi im Hintergrund "geboxt" ? Hmm .. seltsam das Ganze... Aber Anhand deiner Erklärung habe ich folgenden Code aufgestellt. Der Vorteil ist deutlich sichtbar:
Code:
Ich kann über's "Boxing" eine Funktion mit jedem x beliebigen Datentyp aufrufen. Ok, wann und ob ich es brauche wird sich zeigen ...
private void Form1_Load(object sender, EventArgs e)
{ int i = 50; string s = "50"; object o = i; //object o = s; MessageBox.Show(testFunction(o)); } private string testFunction(object o) { return Convert.ToString(o); } |
AW: Heap und Stack - Unterschiede
Zitat:
Aber man deklariere sich einen beliebigen struct und siehe da, man kann toString() aufrufen obwohl das gar nicht deklariert wurde ;-) Zum Beispiel in Java kann man keine generische ArrayList<double> deklarieren, weil die Klasse einen Referenztype haben möchte. Dafür gibt es dann die Wrapperklasse Double (großegeschrieben) ... sowas wollte man sich vll. ersparen. |
AW: Heap und Stack - Unterschiede
Zitat:
Code:
public class List<T> where T: object {
private T[] values; public void Add(T value) { ... } ... }
Code:
public List<string> myStringList;
public List<int> myIntList; ... |
AW: Heap und Stack - Unterschiede
Zitat:
Folgende Ableitungshierachie finde ich irgendwie komisch. object <-- valueType <-- struct <-- int, wobei object eine Klasse und struct (wie der Name schon sagt) keine ist. Alles nach valueType (structs, int, ...) liegt auf dem Stack, alle Verweistypen auf dem Heap, wo liegt object? |
AW: Heap und Stack - Unterschiede
Zitat:
Code:
;)
5.ToString()
// ... oder ... "Hello!".ToUpper() Und SebE: Ein object ist ein Verweistyp -> Heap. Die Value-Typen sind nicht wirklich von object abgeleitet, dort kommt nur wieder das Boxing ins Spiel. |
AW: Heap und Stack - Unterschiede
Zitat:
Das mag am Anfang komisch sein. Aber wenn man darüber Bescheid weiß, ist das halt eine kleine Eigenart die man beachten sollte. In Delphi z.B. ist es nicht ohne weiteres möglich, eine Funktion mit einem Parameter beliebigen Typ zu erstellen. Entweder man überlädt sie x-mal mit TObject, int, long, double, string oder man nimmt einen Variant. Wobei ein Variant mehr Overhead haben dürfte als das boxing. Btw.: Sooo häufig ist mir das Boxing noch nicht in die Quere gekommen seit ich mit C# einwickle. Eigentlich noch nie. Häufig kann man ja statt structs auch Klassen verwenden, dann hat sich das mit dem Boxing eh erübrigt. An vielen Stellen wo geboxt wird, wird die nicht geboxte Kopie auch weggeworfen. Zum Beispiel:
Code:
Jetzt hast du natürlich die Situation dass die Haarfarbe nicht mehr in der Kontaktliste steht. Wenn myStruct eine Klasse wäre, würde es noch übernommen.
Mein_Eventhandler
{ var abc = new(MyStruct); abc.Name = NameTxt.Text; abc.Age = (int)AgeNum.value; Contactlist.Add(abc); abc.Haircolor = Color.Green; } Aber der Code sieht ja auch komisch aus. Erst alle Felder befüllen und dann in die Liste rein ;-) |
AW: Boxing und Unboxing in C#
Es ist übrigens sehr wohl möglich, primitive Datentypen als var- oder out-Parameter zu übergeben. AFAIK wird dabei auch nicht im Hintergrund rumgeboxed, sondern lediglich die Zeigernatur (wie auch in Delphi) via Sprachmitteln verborgen. Ebenso ist die Typsicherheit durch den Compiler gewährt. Alles in allem ist das ja eigentlich "nur" konsequentes OOP Durchziehen, so dass - zumindest semantisch - immer alles Objekte sein können.
Ich bin zudem nicht völlig davon überzeugt, dass die Runtime da hinter den Kulissen wirklich in vollem Umfang ein Objekt nach "allen Regeln der Kunst" aufbaut. Ich mutmaße auch dort eine Portion Compiler- bzw. Runtimemagic mehr, damit solche Dinge nicht allzu teuer werden. Ich war auch zu Beginn von .NET recht skeptisch und hielt das für Humbug, aber man erhält einfach eine Fülle von Freiheiten und Komfort auf diese so einfache Weise, die man wirklich erst merkt, wenn man die 100ste List<float> nicht separat neu ableiten musste :). Und der Preis ist doch eher marginal. Ich habs argwöhnisch erwartet, und lieben gelernt. |
AW: Heap und Stack - Unterschiede
Zitat:
Zum einen sollen sie sich wie Werte auf dem Stack verhalten, also Artefakte, die nur du ihre tatsächlichen Werte bestimmt sind und die dann auch eine Garbage Collection brauchen. Aber andererseits, erwartet man von ihnen, dass sie zuweisungskompatibel zu Object oder gar Interfaces sind, und dabei sogar PolyMorphie unterstützen sollen (ToString, Equals, GetHashCode). Gerade Interfaces sind eine kleine pöhse Falle. Man erwartet es als Anfänger nicht, aber dass hier boxt auch:
Code:
Das CLR-Team hat hier einen ganz guten Job gemacht. Denn wenn man ValueTypes als solche benutzt, verhalten sie sich auch so.
interface ITöröö
{ void Abc(); } struct EinStruct { public void Abc(){} } .... ITöröö xyz = new EinStruct(); Mit allen daraus resultierenden Optimierungen oder auch Performance-Fallen. Aber andererseits brechen sie .Nets "rooted type system" nicht. Also dass alle Typen auf eine Wurzel zurückgehen. Wenn man es denn wirklich braucht, kann man sie in eine Referenz stecken, die ein Interface oder vom Typ Object ist. In dem Moment kann man es auch verschmerzen, dass der ValueType verpackt wird. Denn ohne diesen Mechanismus würde man á la Java nicht weiterkommen. Eine Box ist hierbei vergleichbar mit einer Klasse, die ein Feld vom Typen deines ValueTypes hat und die gleichen Methoden und Properties hat.
Code:
Solch eine Box ist übrigens vergleichbar mit den Value classes aus Java. Also Integer anstatt int.
class PseudoBox : ITöröö
{ private EinStruct DerWert; public void Abc() { DerWert.Abc(); } } Was die CLR aber für uns tut ist, dass all das transparent passiert. Wir sehen die Box nicht. Wenn wir uns xyz per Reflection anschauen, sehen wir alles aus EinStruct, was da auch hingehört. ValueTypes und Boxing sind also Taschenspielertricks der CLR um performante native Typen wie Int32 oder Double verwenden zu können, die aber im Typsystem keine komische Ausnahme spielen, sondern sich genauso einfügen, wie alle selbstdefinierten Typen. Was passiert, wenn man kein Boxing in der Runtime hat sieht man "schön" an Delphi oder Java, denn dort sind native Typen etwas komplett anderes als alle anderen selbst definierbaren Typen. In Java, kann man native Typen nichtmal für Generics nutzen, in Delphi gibt es Record helpers, aber keinerlei Möglichkeit zum Beispiel Integer oder TDateTime mit Helpers zu erweitern. Wobei letzteres nicht unbedingt einen technischen Grund haben muss. Ich denke da war einfach der Anspruch bei Borlemb nicht hoch genug. |
AW: Boxing und Unboxing in C#
Zitat:
Zitat:
Aber der Aufwand der Instanzierung einer Box ist gleichzusetzen mit dem einer Klasse, welche dein Struct als Feld hat. Zaubern kann das CLR Team ja auch nicht. Und sie werden richtige Klassen wohl kaum langsamer instanzieren lassen, nur damit Boxes relativ schneller sind. ;-) |
AW: Boxing und Unboxing in C#
Hm, leuchtet irgendwie ein :) Hätte mir da nur ggf. sowas vorstellen können, wie es z.B. bei Copy-on-Write gemacht wird: Semantisch wird es wie ein Objekt ausgesehen lassen, intern aber so lange als nativer Typ verwaltet, bis man tatsächlich nicht mehr anders kann. So Sachen wie eben "12.ToString()" ließen sich ja umschiffen, ohne da wirklich etwas zu instanziren. Wenn man mal davon absieht, dass die Methode ja eh wie schon erwähnt etwas anders implementiert ist.
Danke dir für die Details! |
AW: Boxing und Unboxing in C#
Zitat:
ValueTypes sind per definition sealed. Das heißt, wenn du sowas in deinem Code stehen hast, kein .callvirt nötig sein. Allerdings würde der JIT in keinem Fall tatsächlich einen klassischen Method lookup machen, der bei virtuellen calls eigentlich nötig wäre, da er weiß, dass 12 sealed ist. Und wennet nich virtuell zugeht, braucht man für den call selbst keine Box. |
AW: Boxing und Unboxing in C#
|
AW: Boxing und Unboxing in C#
Ja, in etwa das wollte ich da gestern ausgedrückt haben, also dass ich das dahinter vermute. Beispiel und Worte waren allerdings nicht so 1a gewählt - das wird zum Ende der Woche hin auch immer schwieriger :)
Die wichtige Aussage ist am Ende glaube ich: Die Jungs haben sich da durchaus einiges bei gedacht, und die Umsetzung ist pfiffig genug, dass man nun nicht befürchten müsste, ständig mit klobigen fetten "echten" Objekten um seine Primitives leben zu müssen. Es ist jedoch nicht schlecht zu wissen, wo und wann Boxing einsetzt, sonst wundert man sich ggf. 1-2 Mal im Jahr was denn grad schief gelaufen ist. So hatte ich mal die Situation, einen Getter ein Element eines MyStruct[] zurückgeben zu lassen: "myObject.Items[i].aField = 0;" hatte dann aber den Wert des Feldes des Items an der Stelle i im MyStruct[] nicht geändert, da der Getter implizit eine Kopie geliefert hat. Daraus ein MyClass[] gemacht, und alles war gut. Ich bin mir allerdings nicht sicher, ob das wirklich mit Boxing zu tun hatte. Es hätte auch einfach eine Kopie weil Valuetype sein können. Seit dem bin ich mit structs in C# zumindest etwas vorsichtiger :) |
Alle Zeitangaben in WEZ +1. Es ist jetzt 06:28 Uhr. |
Powered by vBulletin® Copyright ©2000 - 2025, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024-2025 by Thomas Breitkreuz