Najlepszym sposobem na zrozumienie tego jest spojrzenie na języki programowania niższego poziomu, na których opiera się C #.
W językach najniższego poziomu, takich jak C, wszystkie zmienne idą w jednym miejscu: stosie. Za każdym razem, gdy deklarujesz zmienną, przechodzi ona na stos. Mogą to być tylko prymitywne wartości, takie jak bool, bajt, 32-bitowy int, 32-bitowy uint itp. Stos jest zarówno prosty, jak i szybki. Gdy dodawane są zmienne, po prostu idą jedna na drugą, więc pierwsza deklarowana wartość to 0x00, kolejna 0x01, kolejna 0x02 w pamięci RAM itp. Ponadto zmienne są często wstępnie adresowane podczas kompilacji czas, więc ich adres jest znany jeszcze przed uruchomieniem programu.
Na wyższym poziomie, podobnie jak C ++, wprowadzono drugą strukturę pamięci o nazwie Heap. Nadal w większości mieszkasz na stosie, ale do stosu można dodać specjalne intryny o nazwie Wskaźniki , które przechowują adres pamięci dla pierwszego bajtu obiektu, i ten obiekt żyje na stosie. Sterta jest rodzajem bałaganu i nieco kosztowna w utrzymaniu, ponieważ w przeciwieństwie do zmiennych stosu, nie nakładają się one liniowo w górę, a następnie w dół podczas wykonywania programu. Mogą przychodzić i odchodzić w określonej kolejności, mogą rosnąć i kurczyć się.
Radzenie sobie ze wskaźnikami jest trudne. Są przyczyną wycieków pamięci, przepełnienia bufora i frustracji. C # na ratunek.
Na wyższym poziomie, C #, nie musisz myśleć o wskaźnikach - środowisko .Net (napisane w C ++) myśli o nich dla Ciebie i przedstawia je jako Odwołania do obiektów, a dla wydajności pozwala przechowywać prostsze wartości jak boole, bajty i liczby całkowite jako typy wartości. Pod maską obiekty i rzeczy, które tworzą klasę, trafiają na kosztowne, zarządzane przez pamięć stosy, podczas gdy typy wartości mieszczą się w tym samym stosie, który posiadałeś w C na niskim poziomie - superszybko.
Aby zachować prostotę interakcji między tymi 2 zasadniczo różnymi koncepcjami pamięci (i strategiami przechowywania) z punktu widzenia kodera, typy wartości można w dowolnym momencie łączyć. Boks powoduje, że wartość jest kopiowana ze stosu, umieszczana w obiekcie i umieszczana na stosie - droższa, ale płynna interakcja ze światem odniesienia. Jak wskazują inne odpowiedzi, nastąpi to, gdy na przykład powiesz:
bool b = false; // Cheap, on Stack
object o = b; // Legal, easy to code, but complex - Boxing!
bool b2 = (bool)o; // Unboxing!
Mocną ilustracją przewagi boksu jest sprawdzenie wartości zerowej:
if (b == null) // Will not compile - bools can't be null
if (o == null) // Will compile and always return false
Nasz obiekt o jest technicznie adresem w stosie, który wskazuje na kopię naszego bool b, który został skopiowany na stos. Możemy sprawdzić o dla wartości zerowej, ponieważ bool został zapakowany i tam umieszczony.
Ogólnie rzecz biorąc, powinieneś unikać Boksowania, chyba że jest to potrzebne, na przykład, aby przekazać argument do int / bool / cokolwiek innego. Istnieje kilka podstawowych struktur w .Net, które wciąż wymagają przekazywania Typów Wartości jako obiektów (i dlatego wymagają Boksowania), ale w większości przypadków nigdy nie powinieneś potrzebować Boksowania.
Niewyczerpująca lista historycznych struktur C # wymagających boksu, których należy unikać:
Okazuje się, że system wydarzeń ma naiwny warunek wyścigu i nie obsługuje asynchronizacji. Dodaj problem boksu i prawdopodobnie należy go unikać. (Możesz go zastąpić na przykład systemem zdarzeń asynchronicznych, który korzysta z Generics).
Stare modele wątków i timerów wymuszały na swoich parametrach pole, ale zostały zastąpione przez asynchroniczne / oczekujące, które są znacznie czystsze i wydajniejsze.
Kolekcje .Net 1.1 opierały się całkowicie na boksie, ponieważ pojawiły się przed Generics. Te wciąż się pojawiają w System.Collections. W każdym nowym kodzie powinieneś używać kolekcji z System.Collections.Generic, która oprócz unikania boksu zapewnia również większe bezpieczeństwo typu .
Powinieneś unikać deklarowania lub przekazywania Typów Wartości jako obiektów, chyba że musisz poradzić sobie z powyższymi problemami historycznymi, które wymuszają Boks, i chcesz uniknąć późniejszego spadku wydajności Boksu, gdy będziesz wiedział, że i tak zostanie Boxowany.
Zgodnie z sugestią Mikaela poniżej:
Zrób to
using System.Collections.Generic;
var employeeCount = 5;
var list = new List<int>(10);
Nie to
using System.Collections;
Int32 employeeCount = 5;
var list = new ArrayList(10);
Aktualizacja
Ta odpowiedź początkowo sugerowała, że Int32, Bool itp. Powodują boksowanie, podczas gdy w rzeczywistości są to proste aliasy dla typów wartości. Oznacza to, że .Net ma typy takie jak Bool, Int32, String i C # aliasy do bool, int, string, bez żadnej różnicy funkcjonalnej.