Der beste Weg, dies zu verstehen, besteht darin, sich die untergeordneten Programmiersprachen anzusehen, auf denen C # aufbaut.
In den Sprachen der untersten Ebene wie C gehen alle Variablen an eine Stelle: The Stack. Jedes Mal, wenn Sie eine Variable deklarieren, wird sie auf dem Stapel abgelegt. Sie können nur primitive Werte sein, wie ein Bool, ein Byte, ein 32-Bit-Int, ein 32-Bit-Uint usw. Der Stapel ist sowohl einfach als auch schnell. Wenn Variablen hinzugefügt werden, werden sie einfach übereinander gelegt. Der erste, den Sie deklarieren, befindet sich beispielsweise bei 0x00, der nächste bei 0x01, der nächste bei 0x02 im RAM usw. Außerdem werden Variablen beim Kompilieren häufig voradressiert. Zeit, so dass ihre Adresse bekannt ist, bevor Sie das Programm überhaupt ausführen.
In der nächsten Stufe wird wie in C ++ eine zweite Speicherstruktur namens Heap eingeführt. Sie leben immer noch hauptsächlich im Stapel, aber dem Stapel können spezielle Ints hinzugefügt werden, die als Zeiger bezeichnet werden und die Speicheradresse für das erste Byte eines Objekts speichern, und dieses Objekt befindet sich im Heap. Der Heap ist eine Art Chaos und in der Wartung etwas teuer, da sie sich im Gegensatz zu Stack-Variablen nicht linear auf und ab stapeln, 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 für Speicherlecks, Pufferüberläufe und Frustration. C # zur Rettung.
Auf einer höheren Ebene, C #, müssen Sie nicht über Zeiger nachdenken - das .Net-Framework (in C ++ geschrieben) berücksichtigt diese für Sie und präsentiert sie Ihnen als Verweise auf Objekte. Aus Gründen der Leistung können Sie einfachere Werte speichern wie Bools, Bytes und Ints als Werttypen. Unter der Haube befinden sich Objekte und Dinge, die eine Klasse instanziieren, auf dem teuren, speicherverwalteten Heap, während Werttypen in demselben Stapel abgelegt werden, den Sie in C auf niedriger Ebene hatten - superschnell.
Um die Interaktion zwischen diesen beiden grundlegend unterschiedlichen Speicherkonzepten (und Speicherstrategien) aus Sicht eines Codierers einfach zu halten, können Werttypen jederzeit eingerahmt werden. Durch das Boxen wird der Wert vom Stapel kopiert, in ein Objekt eingefügt und auf den Haufen gelegt - teurere, aber flüssige Interaktion mit der Referenzwelt. Wie andere Antworten zeigen, geschieht dies, wenn Sie beispielsweise sagen:
bool b = false; // Cheap, on Stack
object o = b; // Legal, easy to code, but complex - Boxing!
bool b2 = (bool)o; // Unboxing!
Ein starkes Beispiel für den Vorteil des Boxens ist die Überprü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 Stapel, die auf eine Kopie unseres Bools b verweist, die auf den Heap kopiert wurde. Wir können o auf null prüfen, weil der Bool verpackt und dort abgelegt wurde.
Im Allgemeinen sollten Sie Boxen vermeiden, es sei denn, Sie benötigen es, um beispielsweise ein int / bool / was auch immer als Objekt an ein Argument zu übergeben. Es gibt einige grundlegende Strukturen in .Net, die weiterhin die Übergabe von Werttypen als Objekt erfordern (und daher Boxing erfordern), aber zum größten Teil sollten Sie niemals Boxen benötigen.
Eine nicht erschöpfende Liste historischer C # -Strukturen, für die Boxen erforderlich ist, die Sie vermeiden sollten:
Es stellt sich heraus, dass das Ereignissystem eine Race-Bedingung hat, die naiv verwendet wird, und es unterstützt keine Asynchronisierung. Fügen Sie das Boxproblem hinzu und es sollte wahrscheinlich vermieden werden. (Sie können es beispielsweise durch ein asynchrones Ereignissystem ersetzen, das Generics verwendet.)
Die alten Threading- und Timer-Modelle haben eine Box auf ihre Parameter gezwungen, wurden jedoch durch Async / Warten ersetzt, die weitaus sauberer und effizienter sind.
Die .Net 1.1-Sammlungen stützten sich vollständig auf das Boxen, da sie vor Generics kamen. Diese treten in System.Collections immer noch auf. In jedem neuen Code sollten Sie die Sammlungen von System.Collections.Generic verwenden, die Ihnen neben der Vermeidung des Boxens auch eine stärkere Typensicherheit bieten .
Sie sollten vermeiden, Ihre Werttypen als Objekte zu deklarieren oder zu übergeben, es sei denn, Sie müssen sich mit den oben genannten historischen Problemen befassen, die das Boxen erzwingen, und Sie möchten den Leistungseinbruch des späteren Boxens vermeiden, wenn Sie wissen, dass es sowieso boxen wird.
Per Mikaels Vorschlag unten:
Mach das
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);
Aktualisieren
Diese Antwort schlug ursprünglich vor, dass Int32, Bool usw. Boxen verursachen, obwohl es sich tatsächlich um einfache Aliase für Werttypen handelt. Das heißt, .Net verfügt über Typen wie Bool, Int32, String und C #, die ohne funktionalen Unterschied in Bool, Int, String umgewandelt werden.