Frage

Die .NET 1.0 Art und Weise Sammlung von ganzen Zahlen zu schaffen (zum Beispiel) war:

ArrayList list = new ArrayList();
list.Add(i);          /* boxing   */
int j = (int)list[0]; /* unboxing */

Die Strafe diese zu verwenden, ist der Mangel an Typ Sicherheit und Leistung aufgrund Boxen und Unboxing.

Die .NET 2.0 Art und Weise ist die Verwendung Generika:

List<int> list = new List<int>();
list.Add(i);
int j = list[0];

Der Preis der Boxen (nach meinem Verständnis) ist die Notwendigkeit, ein Objekt auf dem Heap zu erstellen, kopieren Sie den Stapel reserviert ganze Zahl auf das neue Objekt und umgekehrt für Unboxing.

Wie wirkt sich die Verwendung von Generika dies überwinden? Ist der Stapel zugewiesene Ganzzahl bleibt auf dem Stapel und aus dem Heap darauf wird (ich denke, dies ist nicht der Fall ist, weil von dem, was geschehen wird, wenn es Spielraum aussteigen wird)? Es scheint, wie es ist immer noch ein Bedürfnis danach woanders aus dem Stapel zu kopieren.

Was ist wirklich los?

War es hilfreich?

Lösung

Wenn es um Sammlungen kommt, Generika machen es möglich, Boxen zu vermeiden / Unboxing von intern tatsächlichen T[] Arrays verwendet. List<T> zum Beispiel verwendet einen T[] Array seinen Inhalt zu speichern.

Die array , ist natürlich ein Referenztyp und ist daher (in der aktuellen Version der CLR, Blabla) auf dem Heap gespeichert. Aber da es sich um ein T[] ist und kein object[], die Elemente des Array kann „direkt“ gespeichert werden: das heißt, sie sind immer noch auf dem Heap, aber sie sind auf dem Heap in dem Array statt wobei verpackt und mit dem Array Verweis auf die Boxen enthalten.

Also für einen List<int>, zum Beispiel, was Sie im Array haben würde würde „Look“ wie folgt aus:

[ 1 2 3 ]

Vergleichen Sie dies mit einem ArrayList, die eine object[] verwendet und würde daher „Look“ so etwas wie folgt aus:

[ *a *b *c ]

... wo *a usw. sind Verweise auf Objekte (boxed ganze Zahlen):

*a -> 1
*b -> 2
*c -> 3

Entschuldigung, diese rohen Abbildungen; hoffentlich wissen Sie, was ich meine.

Andere Tipps

Ihre Verwirrung ist ein Ergebnis von Missverständnissen, was die Beziehung zwischen dem Stapel ist, der Heap und Variablen. Hier ist der richtige Weg, um darüber nachzudenken.

  • Eine Variable ist eine Speicherstelle, die einen Typ hat.
  • Die Lebensdauer einer Variablen kann entweder kurz oder lang sein. Mit „kurzen“ wir „bis die aktuelle Funktion zurückkehrt oder wirft“ bedeuten und „lang“ meinen wir „kann mehr als das“.
  • Wenn der Typ einer Variablen ein Referenztyp ist dann der Inhalt der Variable ist ein Verweis auf ein langlebiges Lagerort. Wenn der Typ einer Variablen ein Werttyp ist dann der Inhalt der Variable ist ein Wert.

Als Implementierungsdetail, eine Speicherstelle, die garantiert wird kurzlebig sein kann auf dem Stapel zugeordnet werden. Ein Lagerort, die lange gelebt werden könnte, wird auf dem Heap zugeordnet. Beachten Sie, dass dies sagt nichts über „Wertetypen sind immer auf dem Stack zugeordnet.“ Werttypen sind nicht immer auf dem Stack zugeordnet:

int[] x = new int[10];
x[1] = 123;

x[1] ist ein Lagerort. Es ist langlebig; es könnte länger als diese Methode lebt. Deshalb muss es auf dem Heap sein. Die Tatsache, dass es einen int enthält, ist irrelevant.

Sie richtig sagen, warum ein boxed int ist teuer:

  

Der Preis der Boxen ist die Notwendigkeit, ein Objekt auf dem Heap zu erstellen, kopieren Sie den Stapel auf das neue Objekt zugewiesen integer und umgekehrt für Unboxing.

Wo man nichts falsch machen ist „der Stapel zugeordnet integer“ zu sagen. Dabei spielt es keine Rolle, wo die ganze Zahl zugeordnet wurde. Was zählt, war, dass seine Speicher die ganze Zahl enthalten , statt mit ein Verweis auf einen Haufen Lage . Der Preis ist die Notwendigkeit, das Objekt zu erstellen und die Kopie zu tun; Das ist die einzig Kosten, die relevant ist.

Warum also nicht eine generische Variable teuer? Wenn Sie eine Variable vom Typ T haben, und T aufgebaut ist int zu sein, dann haben Sie eine Variable vom Typ int, period. Eine Variable des Typs int ist eine Speicherstelle, und es enthält einen int. Ob das Ablageort auf dem Stapel oder die Heap ist völlig irrelevant . Relevant ist, dass der Lagerort ein int enthält , statt mit ein Hinweis auf etwas auf dem Heap . Da der Ablageort einen int enthält, müssen Sie nicht auf den Kosten der Boxen zu nehmen und Unboxing. Neue Speicher auf dem Heap Aufteilung und die int auf die neuen Speicher Kopieren

Ist das jetzt klar?

Generics ermöglicht die interne Anordnung der Liste int[] statt effektiv object[] eingegeben werden, was Boxen erfordern würde.

Hier ist, was ohne Generika geschieht:

  1. Sie rufen Add(1).
  2. Die ganzzahlige 1 wird in ein Objekt verpackt, die ein neues Objekt erfordert auf dem Heap gebaut werden.
  3. Diese Aufgabe wird an ArrayList.Add() übergeben.
  4. Die Box-Objekt wird in einen object[] gestopft.

Es gibt drei Dereferenzierungsebenen hier:. ArrayList -> object[] -> object -> int

Mit Generika:

  1. Sie rufen Add(1).
  2. Die int 1 auf List<int>.Add() übergeben.
  3. Die int in eine int[] gestopft.

Es gibt also nur zwei Dereferenzierungsebenen. List<int> -> int[] -> int

Ein paar andere Unterschiede:

  • Der nicht gattungsgemäßen Verfahren wird eine Summe von 8 oder 12 Bytes (ein Zeiger, ein int) erfordert den Wert zu speichern, 4/8 in einer Verteilung und 4 in den anderen Seite. Und dies wird wahrscheinlich mehr wegen Ausrichtung und Polsterung. Die generische Methode wird nur 4 Byte Platz in der Anordnung erforderlich ist.
  • Die nicht-generische Methode erfordert eine boxed int zuzuordnen; die generische Methode nicht. Dies ist schneller und reduziert GC Churn.
  • Die nicht-generische Methode erfordert Abgüsse zu Auszugswerte. Dies ist nicht typsicher und es ist ein bisschen langsamer.

Ein Arraylist behandelt nur die Art object so diese Klasse zu verwenden, erfordert vom und zum object Gießen. Im Fall von Werttypen beinhaltet diese Casting Boxen und Unboxing.

Wenn Sie eine generische Liste der Compiler gibt spezialisierte Code für diesen Werttyp, so dass die Ist-Werte in der Liste gespeichert werden, anstatt einen Verweis auf Objekte, die die Werte enthalten. Daher ist keine Boxen erforderlich ist.

  

Der Preis der Boxen (nach meinem Verständnis) ist die Notwendigkeit, ein Objekt auf dem Heap zu erstellen, kopieren Sie den Stapel reserviert ganze Zahl auf das neue Objekt und umgekehrt für Unboxing.

Ich glaube, Sie gehen davon aus, dass Werttypen immer auf dem Stapel instanziiert werden. Dies ist nicht der Fall - sie können entweder auf dem Heap erstellt werden, auf dem Stapel oder in den Registern. Weitere Informationen über diese finden Sie in Eric Lippert Artikel: Die Wahrheit über Wertetypen .

In .NET 1, wenn die Add Methode aufgerufen wird:

  1. wird Platz auf dem Heap zugewiesen werden; eine neue Referenz wird
  2. Der Inhalt der i Variable wird in die Referenz kopiert
  3. Eine Kopie der Referenz am Ende der Liste gesetzt wird,

In .NET 2:

  1. Eine Kopie der Variablen i an die Add Methode übergeben
  2. Eine Kopie dieser Kopie am Ende der Liste gesetzt wird,

Ja, der i Variable noch kopiert wird (immerhin ist es ein Werttyp und Werttypen werden immer kopiert - auch wenn sie nur Methodenparameter sind). Aber es gibt keine redundante Kopie auf dem Heap gemacht.

Warum sind Sie in Bezug auf WHERE Denken der Objekte \ Werte werden gespeichert? In C können # Werttypen auf dem Stapel gespeichert werden sowie Heap je nachdem, was die CLR wählt.

Wo Generika einen Unterschied machen wird WHAT wird in der Sammlung gespeichert. Im Falle von ArrayList enthält die Sammlung Verweise auf geschachtelte Objekte, bei denen als die List<int> int Werte selbst enthält.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top