Dlaczego
Aby mieć zunifikowany system typów i pozwolić, aby typy wartości miały zupełnie inną reprezentację swoich bazowych danych niż sposób, w jaki typy referencyjne reprezentują ich bazowe dane (np. A int
to tylko koszyk trzydziestu dwóch bitów, który jest całkowicie inny niż referencja rodzaj).
Pomyśl o tym w ten sposób. Masz zmienną o
typu object
. A teraz masz int
i chcesz to włożyć o
. o
jest odniesieniem do czegoś, a int
zdecydowanie nie jest odniesieniem do czegoś (w końcu to tylko liczba). Tak więc robisz to: tworzysz nowy, object
który może przechowywać, int
a następnie przypisujesz odwołanie do tego obiektu o
. Ten proces nazywamy „boksowaniem”.
Jeśli więc nie zależy ci na zunifikowanym systemie typów (tj. Typy referencyjne i typy wartości mają bardzo różne reprezentacje i nie chcesz wspólnego sposobu „reprezentowania” tych dwóch), nie potrzebujesz boksu. Jeśli nie zależy ci na int
reprezentowaniu ich wartości bazowej (tj. Zamiast tego int
są też typami referencyjnymi i po prostu przechowują referencje do ich wartości bazowej), nie potrzebujesz boksu.
gdzie powinienem go użyć.
Na przykład stary typ kolekcji ArrayList
zjada tylko object
s. Oznacza to, że przechowuje tylko odniesienia do czegoś, co gdzieś mieszka. Bez boksu nie można umieścić int
takiej kolekcji. Ale w boksie możesz.
Teraz, w czasach ogólnych, tak naprawdę nie jest to potrzebne i ogólnie można wesoło iść bez zastanowienia się nad problemem. Ale należy pamiętać o kilku zastrzeżeniach:
To jest poprawne:
double e = 2.718281828459045;
int ee = (int)e;
To nie jest:
double e = 2.718281828459045;
object o = e; // box
int ee = (int)o; // runtime exception
Zamiast tego musisz to zrobić:
double e = 2.718281828459045;
object o = e; // box
int ee = (int)(double)o;
Najpierw musimy jawnie rozpakować double
( (double)o
), a następnie przesłać to do pliku int
.
Co jest wynikiem:
double e = 2.718281828459045;
double d = e;
object o1 = d;
object o2 = e;
Console.WriteLine(d == e);
Console.WriteLine(o1 == o2);
Pomyśl o tym przez chwilę, zanim przejdziesz do następnego zdania.
Jeśli powiedziałeś True
i False
świetnie! Czekaj, co? Jest tak, ponieważ ==
w przypadku typów referencyjnych używa się równości referencyjnej, która sprawdza, czy referencje są równe, a nie czy podstawowe wartości są równe. Jest to niebezpiecznie łatwy do popełnienia błąd. Może nawet bardziej subtelny
double e = 2.718281828459045;
object o1 = e;
object o2 = e;
Console.WriteLine(o1 == o2);
wydrukuje również False
!
Lepiej powiedzieć:
Console.WriteLine(o1.Equals(o2));
który na szczęście wydrukuje True
.
Ostatnia subtelność:
[struct|class] Point {
public int x, y;
public Point(int x, int y) {
this.x = x;
this.y = y;
}
}
Point p = new Point(1, 1);
object o = p;
p.x = 2;
Console.WriteLine(((Point)o).x);
Jaka jest wydajność? To zależy! Jeśli Point
jest a, struct
to wyjście jest, 1
ale jeśli Point
jest, to class
wyjście jest 2
! Konwersja boksu tworzy kopię wartości w pudełku, wyjaśniając różnicę w zachowaniu.
boxing
iunboxing
?W systemie .NET istnieją dwa rodzaje typów - typy wartości i typy referencyjne. Jest to stosunkowo powszechne w językach OO.
Jedną z ważnych cech języków obiektowych jest umiejętność obsługi instancji w sposób niezależny od typu. Jest to określane jako polimorfizm . Ponieważ chcemy skorzystać z polimorfizmu, ale mamy dwa różne gatunki typów, musi istnieć sposób ich połączenia, abyśmy mogli poradzić sobie z jednym lub drugim w ten sam sposób.
Teraz, w dawnych czasach (1.0 Microsoft.NET), nie było tego nowo opracowanego generycznego hullabaloo. Nie można napisać metody, która ma jeden argument, który mógłby obsłużyć typ wartości i typ odwołania. To naruszenie polimorfizmu. Tak więc boks został przyjęty jako sposób na przymuszenie typu wartości do obiektu.
Gdyby to nie było możliwe, ramy byłyby zaśmiecone metodami i klasami, których jedynym celem było zaakceptowanie innego gatunku. Nie tylko to, ale ponieważ typy wartości tak naprawdę nie mają wspólnego przodka typu, musiałbyś mieć inne przeciążenie metod dla każdego typu wartości (bit, bajt, int16, int32 itp. Itd.).
Boks temu zapobiegał. I dlatego Brytyjczycy świętują drugi dzień świąt.
źródło
List<string>.Enumerator
na,IEnumerator<string>
daje obiekt, który w większości zachowuje się jak typ klasy, ale z uszkodzonąEquals
metodą. Lepszym sposobem oddanychList<string>.Enumerator
doIEnumerator<string>
byłoby wezwać operatora niestandardowych konwersji, ale istnienie domniemanych zapobiega konwersji tego.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:
Mocną ilustracją przewagi boksu jest sprawdzenie wartości zerowej:
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
Nie to
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.
źródło
Boks nie jest tak naprawdę czymś, z czego korzystasz - jest to coś, co używa środowisko wykonawcze, dzięki czemu możesz obsługiwać typy referencji i wartości w ten sam sposób, gdy jest to konieczne. Na przykład, jeśli użyłeś ArrayList do przechowywania listy liczb całkowitych, liczby całkowite zostaną zapakowane, aby zmieściły się w szczelinach typu obiektowego w ArrayList.
Używając teraz ogólnych kolekcji, to prawie znika. Jeśli utworzysz a
List<int>
, boks nie jest wykonywany -List<int>
może on przechowywać liczby całkowite bezpośrednio.źródło
Boksowanie i rozpakowywanie są szczególnie używane do traktowania obiektów typu wartości jako typu odniesienia; przenosząc ich rzeczywistą wartość na zarządzaną stertę i uzyskując dostęp do ich wartości przez odniesienie.
Bez boksu i rozpakowania nigdy nie można przekazywać typów wartości przez odniesienie; a to oznacza, że nie można przekazywać typów wartości jako instancji Object.
źródło
Ostatnim miejscem, w którym musiałem coś rozpakować, było pisanie kodu pobierającego niektóre dane z bazy danych (nie korzystałem z LINQ to SQL , tylko zwykły stary ADO.NET ):
Zasadniczo, jeśli pracujesz ze starszymi interfejsami API przed generycznymi, napotkasz boks. Poza tym nie jest to takie powszechne.
źródło
Boks jest wymagany, gdy mamy funkcję, która potrzebuje obiektu jako parametru, ale mamy różne typy wartości, które należy przekazać, w takim przypadku musimy najpierw przekonwertować typy wartości na typy danych obiektu, zanim przekażemy je do funkcji.
Nie sądzę, że to prawda, spróbuj tego zamiast tego:
To działa dobrze, nie używałem boxowania / rozpakowywania. (Chyba że kompilator robi to za kulisami?)
źródło
W .net każda instancja Object lub dowolnego pochodnego typu zawiera strukturę danych, która zawiera informacje o jej typie. „Prawdziwe” typy wartości w .net nie zawierają takich informacji. Aby umożliwić manipulowanie danymi w typach wartości za pomocą procedur, które oczekują odbierania typów pochodzących od obiektu, system automatycznie określa dla każdego typu wartości odpowiedni typ klasy z tymi samymi elementami i polami. Boks tworzy nowe wystąpienia tego typu klasy, kopiując pola z wystąpienia typu wartości. Rozpakowanie powoduje skopiowanie pól z instancji typu klasy do instancji typu wartości. Wszystkie typy klas, które są tworzone z typów wartości, pochodzą z ironicznie nazwanej klasy ValueType (która pomimo swojej nazwy jest w rzeczywistości typem referencyjnym).
źródło
Gdy metoda przyjmuje tylko typ referencyjny jako parametr (powiedzmy, że metoda ogólna jest ograniczona przez klasę poprzez
new
ograniczenie), nie będzie można przekazać do niego typu referencyjnego i trzeba go zaznaczyć.Dotyczy to również wszelkich metod, które przyjmują
object
jako parametr - będzie to musiał być typ referencyjny.źródło
Ogólnie rzecz biorąc, zazwyczaj będziesz chciał uniknąć boksowania swoich typów wartości.
Są jednak rzadkie przypadki, w których jest to przydatne. Na przykład, jeśli chcesz celować w środowisko 1.1, nie będziesz mieć dostępu do ogólnych zbiorów. Każde użycie kolekcji w .NET 1.1 wymagałoby traktowania twojego typu wartości jako System.Object, co powoduje boksowanie / rozpakowywanie.
Nadal istnieją przypadki, aby było to przydatne w .NET 2.0+. Za każdym razem, gdy chcesz skorzystać z faktu, że wszystkie typy, w tym typy wartości, mogą być traktowane bezpośrednio jako obiekt, może być konieczne użycie boksu / rozpakowania. Może to być czasami przydatne, ponieważ pozwala zapisać dowolny typ w kolekcji (używając obiektu zamiast T w ogólnej kolekcji), ale ogólnie lepiej tego unikać, ponieważ tracisz bezpieczeństwo typu. Jedynym przypadkiem, w którym często pojawia się boks, jest użycie Odbicia - wiele wywołań w odbiciu będzie wymagać boksowania / rozpakowywania podczas pracy z typami wartości, ponieważ ten typ nie jest wcześniej znany.
źródło
Boks to konwersja wartości na typ odniesienia z danymi z pewnym przesunięciem w obiekcie na stercie.
Co do tego, co faktycznie robi boks. Oto kilka przykładów
Mono C ++
Rozpakowywanie w Mono jest procesem rzutowania wskaźnika na przesunięcie 2 gpointerów w obiekcie (np. 16 bajtów). A
gpointer
jest avoid*
. Ma to sens, gdy patrzymy na definicję,MonoObject
ponieważ jest to po prostu nagłówek danych.C ++
Aby wstawić wartość w C ++, możesz zrobić coś takiego:
źródło