Der beste Weg, dies zu verstehen, ist ein Blick auf niedrigere Programmiersprachen, auf denen C# aufbaut.
In den niedrigsten Sprachen wie C werden alle Variablen an einem Ort gespeichert: Der Stapel. Jedes Mal, wenn Sie eine Variable deklarieren, kommt sie auf den Stack. Dabei kann es sich nur um primitive Werte handeln, wie bool, byte, 32-bit int, 32-bit uint, usw. Der Stack ist sowohl einfach als auch schnell. Wenn Variablen hinzugefügt werden, kommen sie einfach übereinander, so dass die erste Variable, die Sie deklarieren, z. B. bei 0x00 liegt, die nächste bei 0x01, die nächste bei 0x02 im RAM, usw. Außerdem sind die Variablen oft schon bei der Kompilierung voradressiert, so dass ihre Adresse schon bekannt ist, bevor Sie das Programm starten.
In der nächsthöheren Stufe, wie C++, wird eine zweite Speicherstruktur, der Heap, eingeführt. Sie leben immer noch hauptsächlich im Stack, aber spezielle Ints namens Wegweiser können dem Stack hinzugefügt werden, die die Speicheradresse für das erste Byte eines Objekts speichern, und dieses Objekt befindet sich im Heap. Der Heap ist ein ziemliches Durcheinander und etwas teuer in der Wartung, denn im Gegensatz zu Stack-Variablen stapeln sie sich nicht linear auf und ab, wenn ein Programm ausgeführt wird. Sie können in keiner bestimmten Reihenfolge kommen und gehen, und sie können wachsen und schrumpfen.
Der Umgang mit Zeigern ist schwierig. Sie sind die Ursache von Speicherlecks, Pufferüberläufen und Frustration. C# als Retter in der Not.
Auf einer höheren Ebene, in C#, müssen Sie sich keine Gedanken über Zeiger machen - das (in C++ geschriebene) .Net-Framework denkt für Sie darüber nach und stellt sie Ihnen als Verweise auf Objekte dar, und aus Leistungsgründen können Sie einfachere Werte wie Bools, Bytes und Ints als Value Types speichern. Unter der Haube werden Objekte und Dinge, die eine Klasse instanziieren, auf dem teuren, speicherverwalteten Heap abgelegt, während Werttypen in demselben Stack gespeichert werden, den Sie auch in low-level C hatten - superschnell.
Um die Interaktion zwischen diesen beiden grundlegend unterschiedlichen Konzepten von Speicher (und Strategien für die Speicherung) aus der Sicht eines Programmierers einfach zu halten, können Wertetypen jederzeit in Boxen gespeichert werden. Boxing bewirkt, dass der Wert vom Stapel kopiert, in ein Objekt eingefügt und auf dem Heap abgelegt wird - teurer, aber flüssige Interaktion mit der Referenzwelt. Wie andere Antworten darauf hinweisen, wird dies auftreten, wenn Sie zum Beispiel sagen:
bool b = false; // Cheap, on Stack
object o = b; // Legal, easy to code, but complex - Boxing!
bool b2 = (bool)o; // Unboxing!
Ein deutliches Beispiel für den Vorteil des Boxens ist die Prüfung auf Null:
if (b == null) // Will not compile - bools can't be null
if (o == null) // Will compile and always return false
Unser Objekt o ist technisch gesehen eine Adresse im Stack, die auf eine Kopie unseres bool b zeigt, das auf den Heap kopiert wurde. Wir können o auf Null prüfen, weil das bool in eine Box gepackt und dort abgelegt wurde.
Im Allgemeinen sollten Sie Boxing vermeiden, es sei denn, Sie brauchen es, zum Beispiel um ein int/bool/whatever als Objekt an ein Argument zu übergeben. Es gibt einige grundlegende Strukturen in .Net, die immer noch die Übergabe von Werttypen als Objekt (und damit Boxing) erfordern, aber in den meisten Fällen sollten Sie nie Boxing benötigen.
Eine nicht erschöpfende Liste historischer C#-Strukturen, die Boxing erfordern und die Sie vermeiden sollten:
-
Das Ereignissystem stellt sich heraus, dass es eine Race Condition gibt in naiver Verwendung, und es unterstützt keine async. Fügen Sie das Boxing-Problem hinzu und es sollte wahrscheinlich vermieden werden. (Sie könnten es zum Beispiel durch ein asynchrones Ereignissystem ersetzen, das Generics verwendet).
-
Die alten Threading- und Timer-Modelle erzwangen eine Box für ihre Parameter, wurden aber durch async/await ersetzt, die weitaus sauberer und effizienter sind.
-
Die .Net 1.1 Collections basierten vollständig auf Boxing, weil sie vor den Generics kamen. Diese sind immer noch in System.Collections zu finden. In jedem neuen Code sollten Sie die Collections von System.Collections.Generic verwenden, die neben der Vermeidung von Boxen auch eine höhere Typensicherheit bieten .
Sie sollten es vermeiden, Ihre Wertetypen als Objekte zu deklarieren oder zu übergeben, es sei denn, Sie müssen sich mit den oben genannten historischen Problemen auseinandersetzen, die das Boxing erzwingen, und Sie wollen den Leistungsverlust durch das spätere Boxing vermeiden, wenn Sie wissen, dass es sowieso geboxt wird.
Wie von Mikael unten vorgeschlagen:
Dies tun
using System.Collections.Generic;
var employeeCount = 5;
var list = new List<int>(10);
Nicht das
using System.Collections;
Int32 employeeCount = 5;
var list = new ArrayList(10);
Update
In dieser Antwort wurde ursprünglich angenommen, dass Int32, Bool usw. Boxing verursachen, obwohl sie in Wirklichkeit nur Aliase für Werttypen sind. Das heißt, .Net hat Typen wie Bool, Int32, String, und C# Aliase sie zu Bool, Int, String, ohne jede funktionale Unterschied.