Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Programmieren allgemein (https://www.delphipraxis.net/40-programmieren-allgemein/)
-   -   Boxing und Unboxing in C# (https://www.delphipraxis.net/162513-boxing-und-unboxing-c.html)

-187- 25. Aug 2011 17:15

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:
            int i = 321;
            object o = i; //boxed
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?

Dankeschön :thumb:

himitsu 25. Aug 2011 17:38

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.

SebE 25. Aug 2011 17:52

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)

-187- 25. Aug 2011 17:55

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 ?

SebE 25. Aug 2011 17:58

AW: Heap und Stack - Unterschiede
 
Delphi-Quellcode:
var
  i, j: integer;

i := 5;
j := i;
i := 10;

// i = 10, j = 5
Wenn es darauf ankommt, dass j IMMER den selben Wert hat wie i, dann brauchst du einen Zeiger.

Delphi-Quellcode:
var
  i: integer;
  j: ^integer;

i := 5;
j := @i;
i := 10;

// i = 10, j^ = 10
Hinweis: hier (Delphi-Beispiel) wandert nichts in den Heap!

-187- 25. Aug 2011 18:20

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

jfheins 25. Aug 2011 18:41

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 :-)

-187- 25. Aug 2011 18:45

AW: Heap und Stack - Unterschiede
 
Zitat:

Zitat von jfheins (Beitrag 1119665)
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.

Okay das macht schonmal Sinn :thumb:

Zitat:

Zitat von jfheins (Beitrag 1119665)
Und diese kann dann auch auf ihren Parameter .toString() aufrufen, da diese Funktion ja in object deklariert ist.

Ich kann auch wie folgt zugreifen:

Delphi-Quellcode:
int i = 0;
string s = i.ToString();

Zitat:

Zitat von jfheins (Beitrag 1119665)
Eine List<int> und eine List<double> benutzen also die gleiche List<T> wie List<String> oder List<StringBuilder>

Das hab ich nicht verstanden ? :pale:

SebE 25. Aug 2011 18:49

AW: Heap und Stack - Unterschiede
 
Ich habe mir das mit dem Boxing nicht so heftig/blöd vorgestellt.

http://social.msdn.microsoft.com/For...-1072384266dd/
Sowas sollte man doch verbieten, oder?

SebE 25. Aug 2011 18:53

AW: Heap und Stack - Unterschiede
 
Zitat:

Zitat von -187- (Beitrag 1119667)
Ich kann auch wie folgt zugreifen:

Delphi-Quellcode:
int i = 0;
string s = i.ToString();

Ich denke hier wird implizit das Boxing angewandt.
Es geht auch kürzer:
Code:
(5).ToString()

-187- 25. Aug 2011 18:56

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:
        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);
        }
Ich kann über's "Boxing" eine Funktion mit jedem x beliebigen Datentyp aufrufen. Ok, wann und ob ich es brauche wird sich zeigen ...

jfheins 25. Aug 2011 19:01

AW: Heap und Stack - Unterschiede
 
Zitat:

Zitat von SebE (Beitrag 1119673)
Delphi-Quellcode:
int i = 0;
string s = i.ToString();
Ich denke hier wird implizit das Boxing angewandt.
Es geht auch kürzer:
Code:
(5).ToString()

Da der Typ Int32 zum Glück die .toString() Methode überschreibt, wird hier nicht geboxt. Also schlechtes Beispiel ^^

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.

SebE 25. Aug 2011 19:04

AW: Heap und Stack - Unterschiede
 
Zitat:

Zitat von -187- (Beitrag 1119667)
Zitat:

Zitat von jfheins (Beitrag 1119665)
Eine List<int> und eine List<double> benutzen also die gleiche List<T> wie List<String> oder List<StringBuilder>

Das hab ich nicht verstanden ? :pale:

Wie in deinem kleinen ShowMessage-Beispiel:
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;
...

SebE 25. Aug 2011 21:29

AW: Heap und Stack - Unterschiede
 
Zitat:

Zitat von jfheins (Beitrag 1119678)
Da der Typ Int32 zum Glück die .toString() Methode überschreibt, wird hier nicht geboxt. Also schlechtes Beispiel ^^

Aber man deklariere sich einen beliebigen struct und siehe da, man kann toString() aufrufen obwohl das gar nicht deklariert wurde ;-)

Ja, da hast du recht. Auch Int, Bool, ... sind - wie alle Structs - von object abgeleitet (und besitzen u.a. ToString()).

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?

implementation 25. Aug 2011 21:57

AW: Heap und Stack - Unterschiede
 
Zitat:

Zitat von SebE (Beitrag 1119673)
Ich denke hier wird implizit das Boxing angewandt.
Es geht auch kürzer:
Code:
(5).ToString()

Es geht auch noch kürzer:
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.

jfheins 25. Aug 2011 22:12

AW: Heap und Stack - Unterschiede
 
Zitat:

Zitat von SebE (Beitrag 1119693)
Ja, da hast du recht. Auch Int, Bool, ... sind - wie alle Structs - von object abgeleitet (und besitzen u.a. ToString()).

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?

object liegt auf dem Heap, wie jeder andere Verweistyp. Falls ein Wert-Typ nach object gecastet wird, wird automatisch ein Objekt erstellt dass den Werttyp beinhaltet.

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:
Mein_Eventhandler
{
var abc = new(MyStruct);
abc.Name = NameTxt.Text;
abc.Age = (int)AgeNum.value;
Contactlist.Add(abc);
abc.Haircolor = Color.Green;
}
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.
Aber der Code sieht ja auch komisch aus. Erst alle Felder befüllen und dann in die Liste rein ;-)

Medium 25. Aug 2011 22:14

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.

Elvis 25. Aug 2011 22:47

AW: Heap und Stack - Unterschiede
 
Zitat:

Zitat von implementation (Beitrag 1119699)
Die Value-Typen sind nicht wirklich von object abgeleitet, dort kommt nur wieder das Boxing ins Spiel.

Doch sind sie, allerdings haben sie eine Identitätskrise.
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:
interface ITöröö

  void Abc();
}

struct EinStruct

  public void Abc(){}
}
....
ITöröö xyz = new EinStruct();
Das CLR-Team hat hier einen ganz guten Job gemacht. Denn wenn man ValueTypes als solche benutzt, verhalten sie sich auch so.
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:
class PseudoBox : ITöröö
{
   private EinStruct DerWert;
   
  public void Abc()
  {
    DerWert.Abc();
  }
}
Solch eine Box ist übrigens vergleichbar mit den Value classes aus Java. Also Integer anstatt int.
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.

Elvis 25. Aug 2011 22:55

AW: Boxing und Unboxing in C#
 
Zitat:

Zitat von Medium (Beitrag 1119704)
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.

ref parameter sind tatsächlich Zeiger. Aber das macht die CLR nur mit Parametern. (Weshalb man zum Beispiel keine ref-Parameter für Iteratoren nutzen kann. Denn dort landen diese ja als Felder, und Felder dürfen keine Referenzen sein.


Zitat:

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.
Natürlich ist eine Box keine eigene Klasse, das wäre ja kaum zu verschleiern und dadurch nicht transparent. Außerdem braucht die Box keine eigenen Metadaten oder Implementierungen der Methoden und spart damit auch einen Batzen an Verwaltung.
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. ;-)

Medium 26. Aug 2011 00:04

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!

Elvis 26. Aug 2011 06:20

AW: Boxing und Unboxing in C#
 
Zitat:

Zitat von Medium (Beitrag 1119719)
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!

12.ToString() braucht keine Box.
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.

pustekuchen 26. Aug 2011 06:42

AW: Boxing und Unboxing in C#
 
Crosspost

Medium 26. Aug 2011 08:39

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