Po co nam boks i unboxing w C#?

Po co nam boks i unboxing w C#?

Wiem, czym jest boks i unboxing, ale nie mogę pojąć, jak naprawdę to wykorzystać. Dlaczego i gdzie mam go używać?
short s = 25;

object objshort = s;  //Boxing

short anothershort = (short)objshort;  //Unboxing
 285
Author: kame, 2010-01-21

10 answers

Dlaczego

Mieć jednolity system typów i pozwolić typom wartości mieć zupełnie inną reprezentację swoich bazowych danych od sposobu, w jaki typy referencyjne reprezentują swoje bazowe dane (np. int jest tylko kubełkiem trzydziestu dwóch bitów, który jest zupełnie inny niż typ odniesienia).

Pomyśl o tym w ten sposób. Masz zmienną o Typu object. A teraz masz int i chcesz umieścić go w o. o jest odniesieniem do coś gdzieś, a int dobitnie nie jest odniesieniem do czegoś gdzieś (w końcu to tylko liczba). Więc robisz to: tworzysz nowy object, który może przechowywać int, a następnie przypisujesz odniesienie do tego obiektu do o. Proces ten nazywamy " boksem."

Tak więc, jeśli 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), to nie potrzebuję boksu. Jeśli nie zależy ci na tym, aby int reprezentowały ich podstawową wartość (tzn. zamiast tego int były typami referencyjnymi i przechowywały tylko odniesienie do ich podstawowej wartości), nie potrzebujesz boksu.

Gdzie mam 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 w takiej kolekcji. Ale z boksem, ty może.

Teraz, w czasach generyków tak naprawdę nie potrzebujesz tego i ogólnie możesz iść wesoło bez myślenia o problemie. Ale jest kilka zastrzeżeń, których należy pamiętać: {]}

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 wyraźnie rozpakować double ((double)o) a potem wrzuć to do int.

Jaki jest wynik:

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 dla drugi przed przejściem do następnego zdania.

Gdybyś powiedział, że świetnie! Czekaj, co? To dlatego, że == na typach referencyjnych używa reference-equality, które sprawdza, czy odniesienia są równe, a nie jeśli wartości bazowe są równe. To niebezpiecznie łatwy błąd do popełnienia. Może nawet bardziej subtelne
double e = 2.718281828459045;
object o1 = e;
object o2 = e;
Console.WriteLine(o1 == o2);

Będzie również drukować False!

Lepiej powiedzieć:

Console.WriteLine(o1.Equals(o2));

Które na szczęście wydrukują True.

One last 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);

Jaki jest wynik? To zależy! Jeśli Point jest struct, to wyjście jest 1, ale jeśli Point jest class, to wyjście jest 2! Konwersja boksu tworzy kopię wartości pudełkowej wyjaśniając różnicę w zachowaniu.

 429
Author: jason,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-12-03 07:19:56

W. NET framework istnieją dwa gatunki typów-typy wartości i typy odniesienia. Jest to stosunkowo powszechne w językach OO.

Jedną z ważnych cech języków obiektowych jest możliwość obsługi instancji w sposób typowo-agnostyczny. Jest to określane jako polimorfizm . Ponieważ chcemy wykorzystać polimorfizm, ale mamy dwa różne gatunki typów, musi być jakiś sposób, aby je połączyć, abyśmy mogli poradzić sobie z jednym lub drugim w ten sam sposób.

Now, back in the olden days (1.0 of Microsoft.NET), nie było tego newfangled generics hullabaloo. Nie można napisać metody, która miała pojedynczy argument, który mógłby obsługiwać typ wartości i typ odniesienia. To naruszenie polimorfizmu. Tak więc Boks został przyjęty jako środek przymusu 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 innych gatunków Typ. Nie tylko to, ale ponieważ typy wartości nie mają wspólnego przodka typu, musiałbyś mieć inne przeciążenie metod dla każdego typu wartości(bit, bajt, int16, int32, itd itd.).

Boks zapobiegł temu. I dlatego Brytyjczycy świętują Dzień boksu.

 45
Author: Will,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-01-21 19:20:28

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 znajdują się w jednym miejscu: stos. Za każdym razem, gdy zadeklarujesz zmienną, trafia 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 zmienne są dodawane, po prostu idą jedna na drugiej, więc pierwsza zadeklarowana siedzi na powiedzmy, 0x00, Następna na 0x01, Następna przy 0x02 w pamięci RAM itp. Ponadto zmienne są często wstępnie adresowane w czasie kompilacji, więc ich adres jest znany przed uruchomieniem programu.

Na kolejnym poziomie, podobnie jak w C++, wprowadza się drugą strukturę pamięci zwaną stertą. Nadal w większości żyjesz w stosie, ale do stosu można dodać specjalne wskaźniki o nazwie Pointers , które przechowują adres pamięci dla pierwszego bajtu obiektu, a ten obiekt żyje w stercie. Sterta jest trochę bałagan i trochę drogie aby utrzymać, ponieważ w przeciwieństwie do zmiennych stosu nie układają się liniowo w górę, a następnie w dół, gdy program wykonuje. Mogą przychodzić i odchodzić w żadnej konkretnej kolejności, mogą rosnąć i kurczyć się.

Radzenie sobie ze wskaźnikami jest trudne. Są przyczyną wycieków pamięci, przekroczenia bufora i frustracji. C# na ratunek.

Na wyższym poziomie, C#, nie musisz myśleć o wskaźnikach -. Net framework (napisany w C++) myśli o nich za Ciebie i przedstawia je jako odniesienia do Obiekty i dla wydajności pozwalają przechowywać prostsze wartości, takie jak Boole, bajty i INT jako typy wartości. Pod maską obiekty i rzeczy tworzące instancje klasy trafiają na kosztowną stertę zarządzaną pamięcią, podczas gdy typy wartości trafiają do tego samego stosu, który miałeś w niskopoziomowym C-superszybkim.

Aby interakcja pomiędzy tymi dwoma zasadniczo różnymi pojęciami pamięci (i strategiami przechowywania) była prosta z punktu widzenia kodera, typy wartości mogą być w każdej chwili Pudełkowane. Boks powoduje, że wartość jest kopiowana ze stosu, wkładana do obiektu 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ą zalet boksu jest sprawdzanie null:

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 stertę. Możemy sprawdzić o dla null bo bool został zapakowany i tam umieszczony.

Ogólnie powinieneś unikać boksu, chyba że potrzebujesz go, na przykład, aby przekazać int / bool / whatever jako obiekt do argumentu. Istnieje kilka podstawowych struktur w. Net, które nadal wymagają przekazywania typów wartości jako obiektów( a więc wymagają boksu), ale w większości przypadków nie powinno być potrzeby boksu.

Niewyczerpująca lista historycznych struktur C#, które wymagają boksu, których powinieneś unikać:

  • System eventowy okazuje się, że ma stan rasy w naiwnym użyciu i nie obsługuje asynchronizacji. Dodaj problem boksu i prawdopodobnie należy go unikać. (Można go zastąpić na przykład asynchronicznym systemem zdarzeń, który używa generycznych.)

  • Stare modele gwintowania i timera wymusiły na swoich parametrach pole, ale zostały zastąpione przez asynchroniczne / oczekujące, które są o wiele czystsze i bardziej wydajne.

  • Kolekcje. Net 1.1 opierały się w całości na boksie, ponieważ przyszedł przed generykami. Te wciąż krążą w systemie.Kolekcje. W każdym nowym kodzie powinieneś używać kolekcji z systemu.Kolekcje.Generyczny, który oprócz unikania boksu zapewnia również silniejsze bezpieczeństwo typu.

Powinieneś unikać deklarowania lub przekazywania typów wartości jako obiektów, chyba że masz do czynienia z powyższymi problemami historycznymi, które wymuszają boks, i chcesz uniknąć uderzenia wydajności boksu później, gdy wiesz i tak będzie zapakowany.

Za 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);

Update

Ta odpowiedź pierwotnie sugerowała Int32, Bool etc cause boxing, kiedy 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# aliasuje je do bool, int, string, bez żadnej różnicy funkcjonalnej.

 25
Author: Chris Moschini,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-02-06 20:41:41

Boks nie jest czymś, czego używasz - jest czymś, czego używa runtime, 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 do przechowywania listy liczb całkowitych użyto ArrayList, liczby całkowite zostały ustawione w polu tak, aby zmieściły się w szczelinach typu Obiektowego w ArrayList.

Używając teraz zbiorów generycznych, to praktycznie znika. Jeśli utworzysz List<int>, nie będzie żadnego boksu - {[0] } może trzymać liczby całkowite bezpośrednio.

 19
Author: Ray,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-01-21 18:43:59

Boks i Unboxing są specjalnie używane do traktowania obiektów typu wartości jako typu odniesienia; przenoszenie ich rzeczywistej wartości do sterty zarządzanej i uzyskiwanie dostępu do ich wartości przez odniesienie.

Bez boksowania i rozpakowywania nie można przekazać typów wartości przez odniesienie; a to oznacza, że nie można przekazać typów wartości jako instancji obiektu.

 11
Author: STW,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-01-21 18:42:19

Ostatnim miejscem, gdzie musiałem coś rozpakować, było pisanie kodu, który pobierał dane z bazy danych (nie używałem LINQ do SQL , tylko zwykły stary ADO.NET):

int myIntValue = (int)reader["MyIntValue"];

Zasadniczo, jeśli pracujesz ze starszymi API przed generykami, napotkasz Boks. Poza tym, to nie jest takie powszechne.

 6
Author: BFree,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-11-30 12:58:37

Boks jest wymagany, gdy mamy funkcję, która potrzebuje obiektu jako parametru, ale mamy różne typy wartości, które muszą zostać przekazane, w takim przypadku musimy najpierw przekonwertować typy wartości na typy danych obiektów przed przekazaniem ich do funkcji.

Nie sądzę, że to prawda, spróbuj zamiast tego:

class Program
    {
        static void Main(string[] args)
        {
            int x = 4;
            test(x);
        }

        static void test(object o)
        {
            Console.WriteLine(o.ToString());
        }
    }
To działa dobrze, nie używałem boksu/unboxingu. (Chyba, że kompilator robi to za kulisami?)
 4
Author: Manoj,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-03-12 05:41:28

W. NET, każda instancja obiektu lub dowolny typ z niej pochodzący zawiera strukturę danych, która zawiera informacje o jego typie. "Rzeczywiste" typy wartości w. Net nie zawierają takich informacji. Aby umożliwić manipulowanie danymi w typach wartości przez procedury, które oczekują odbioru typów pochodzących z obiektu, system automatycznie definiuje dla każdego typu wartości odpowiedni typ klasy z tymi samymi elementami i polami. Boks tworzy nowe instancje tego typu klasy, kopiując pola z instancji typu value. Rozpakowywanie kopiuje pola z instancji typu class do instancji typu value. 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).

 1
Author: supercat,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-09-13 06:43:34

Gdy metoda przyjmuje tylko Typ odniesienia jako parametr (powiedzmy, że metoda ogólna ograniczona do klasy poprzez ograniczenie new), nie będziesz w stanie przekazać do niej typu odniesienia i będziesz musiał go boxować.

Jest to również prawdziwe dla wszelkich metod, które przyjmują object jako parametr - to będzie miało jako typ odniesienia.

 0
Author: Oded,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-01-21 18:44:51

Ogólnie rzecz biorąc, zazwyczaj będziesz chciał uniknąć boksowania typów wartości.

Istnieją jednak rzadkie przypadki, w których jest to przydatne. Na przykład, jeśli musisz kierować się frameworkiem 1.1, nie będziesz mieć dostępu do kolekcji generycznych. Każde użycie kolekcji w. NET 1.1 wymagałoby traktowania typu wartości jako systemu.Obiekt, który powoduje Boks/unboxing.

Nadal istnieją przypadki, że jest to przydatne w. NET 2.0+. W każdej chwili chcesz skorzystać z faktu aby wszystkie typy, w tym typy wartości, można traktować bezpośrednio jako obiekt, konieczne może być użycie boksu/unboxingu. Może to być czasami przydatne, ponieważ pozwala zapisać dowolny typ w kolekcji( używając object zamiast T w ogólnej kolekcji), ale ogólnie lepiej tego uniknąć, ponieważ tracisz bezpieczeństwo typu. Jedynym przypadkiem, w którym Boks często występuje, jest użycie Reflection - wiele wywołań w reflection będzie wymagało boksu/unboxingu podczas pracy z wartością rodzaje, ponieważ Typ nie jest znany z góry.

 0
Author: Hunain,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-10-31 20:14:05