Dlaczego struktury muszą być budowane?

W C# Dowolna zdefiniowana przez użytkownika struct jest automatycznie podklasą systemu.Struct System.ValueType i System.Struct System.ValueType jest podklasą System.Object.

Ale kiedy przypisujemy jakąś strukturę do referencji typu obiektu, zostanie ona zablokowana. Na przykład:

struct A
{
    public int i;
}

A a;
object obj = a;  // boxing takes place here

Więc moje pytanie brzmi: Jeśli A jest potomkiem System.Object, czy kompilator nie może przerzucić go na typ obiektu zamiast boksu?

Author: stakx, 2009-12-30

6 answers

Struktura jest typem wartości. System.Object jest typem odniesienia. Typy wartości i typy referencji są przechowywane i traktowane w inny sposób przez runtime. Aby Typ wartości był traktowany jako typ odniesienia, konieczne jest jego boxowanie. Z perspektywy niskiego poziomu obejmuje to kopiowanie wartości ze stosu, na którym pierwotnie znajdowała się, do nowo przydzielonej pamięci na stosie, która również zawiera nagłówek obiektu. Dodatkowe nagłówki są niezbędne dla typów referencyjnych, aby rozwiązać ich vtables do włączanie wirtualnych dyspozytorów metod i innych funkcji związanych z typem referencyjnym (pamiętaj, że struktura na stosie jest tylko wartością i zawiera informacje o typie zerowym; nie zawiera niczego takiego jak vtables i nie może być bezpośrednio używana do dynamicznego rozwiązywania metod). Poza tym, aby traktować coś jako typ odniesienia, musisz mieć odniesienie (Wskaźnik) do niego, a nie jego wartość surową.

Więc moje pytanie brzmi-Czy a jest potomkiem systemu.Obiekt, nie można kompilować upcast to obiekt typu zamiast boksu?

Na niższym poziomie wartość niczego nie dziedziczy. Właściwie, jak już mówiłem, to nie jest przedmiot. Fakt, że a wywodzi się z System.ValueType, Które z kolei wywodzi się z System.Object, jest czymś zdefiniowanym na poziomie abstrakcji Twojego języka programowania (C#), A C# rzeczywiście ukrywa operację bokserską przed tobą całkiem dobrze. Nie wspominasz o niczym jawnie, aby boxować wartość, więc możesz po prostu myśleć, że kompilator "upcasted" struktura dla Ciebie. Tworzy iluzję dziedziczenia i polimorfizmu dla wartości, podczas gdy żadne z narzędzi wymaganych do zachowania polimorficznego nie jest przez nie dostarczane bezpośrednio.

 37
Author: Mehrdad Afshari,
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
2009-12-30 06:09:32

Oto jak wolę o tym myśleć. Rozważmy implementację zmiennej zawierającej 32-bitową liczbę całkowitą. Gdy traktowana jest jako typ wartości, cała wartość mieści się w 32 bitach pamięci. Tym właśnie jest typ wartości: magazyn zawiera tylko te bity, które składają się na wartość, nic więcej, nic mniej.

Rozważmy teraz implementację zmiennej zawierającej odniesienie do obiektu. Zmienna zawiera "referencję", która może być zaimplementowana na wiele sposobów. Może być uchwyt do struktury garbage collector, albo może to być adres na stercie zarządzanej, albo cokolwiek. Ale to coś, co pozwala znaleźć obiekt. Tym właśnie jest typ odniesienia: pamięć masowa powiązana ze zmienną typu odniesienia zawiera pewne bity, które umożliwiają odwołanie się do obiektu.

Najwyraźniej te dwie rzeczy są zupełnie inne.

Załóżmy teraz, że masz zmienną typu object i chcesz skopiować do niej zawartość zmiennej typu int. Jak ty to robisz? 32 bity, które tworzą liczbę całkowitą, nie są jedną z tych" referencyjnych " rzeczy, to po prostu wiadro, które zawiera 32 bity. Odniesienia mogą być 64-bitowymi wskaźnikami do zarządzanej sterty lub 32-bitowymi uchwytami do struktury danych garbage collector, lub dowolną inną implementacją, o której możesz pomyśleć, ale 32-bitowa liczba całkowita może być tylko 32-bitową liczbą całkowitą.

Więc to, co robisz w tym scenariuszu, to ustawiasz liczbę całkowitą: tworzysz nowy obiekt, który zawiera pamięć dla liczby całkowitej, a następnie przechowujesz odniesienie do nowego obiektu.

Boks jest potrzebny tylko wtedy, gdy chcemy (1) mieć zunifikowany system typów i (2) upewnić się, że 32-bitowa liczba całkowita zużywa 32 bity pamięci. Jeśli jesteś skłonny odrzucić któreś z nich, to nie potrzebujesz boksu; my nie jesteśmy skłonni odrzucić tych, a więc boks jest tym, z czym jesteśmy zmuszeni żyć.

 17
Author: Eric Lippert,
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
2009-12-30 07:32:05

Podczas gdy projektanci. NET z pewnością nie musieli dołączać sekcji 4.3 specyfikacji języka C# wyjaśnia intencje stojące za tym całkiem dobrze, IMO:

Boks i unboxing umożliwia unifikację widok systemu typów, w którym a wartość dowolnego typu może ostatecznie być traktowany jako przedmiot.

Ponieważ typy wartości nie są typami odniesienia (który System.Obiekt ostatecznie jest), akt boksu istnieje, aby mieć jednolity system typów gdzie wartość cokolwiek może być reprezentowana jako obiekt.

Różni się to od np. C++, gdzie system typów nie jest zunifikowany, nie ma wspólnego typu bazowego dla wszystkich typów.

 4
Author: casperOne,
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
2009-12-30 18:47:18

struct jest typem wartości według projektu, dlatego musi być boxed po przekształceniu w typ odniesienia. struct wywodzi się z System.ValueType, które w terminach wywodzi się z System.Object.

Sam fakt, że struct jest potomkiem obiektu, niewiele znaczy..ponieważ CLR traktuje structs inaczej w czasie wykonywania niż typ odniesienia.
 0
Author: Stan R.,
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
2009-12-30 05:50:56

Po odpowiedzi na pytanie przedstawię mały "trick" związany z tym tematem:

structs może implementować interfejsy. Jeśli przekazujesz typ wartości do funkcji, która oczekuje interfejsu, który ten typ wartości implementuje, wartość zostanie normalnie ustawiona w polu. Za pomocą generyków można uniknąć boksu:

interface IFoo {...}
struct Bar : IFoo {...}

void boxing(IFoo x) { ... }
void byValue<T>(T x) : where T : IFoo { ... }

var bar = new Bar();
boxing(bar);
byValue(bar);
 0
Author: helium,
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
2009-12-30 09:47:36

"jeśli struct A jest potomkiem System.Object, Czy kompilator nie może go wrzucić zamiast boksu?"

Nie, po prostu dlatego, że zgodnie z definicją języka C#, "up-casting" w tym przypadku to Boks.

Specyfikacja języka C# zawiera (w rozdziale 13) katalog wszystkich możliwych konwersji typu. Wszystkie te konwersje są kategoryzowane w określony sposób (np. konwersje numeryczne, konwersje referencyjne, itd.).

  1. Istnieją niejawne konwersje typu z typu S na jego super-typ T, ale są one zdefiniowane tylko dla wzorca "Z typu klasy S na typ odniesienia T". Ponieważ twoja struct A nie jest typem klasy, te konwersje nie mogą być zastosowane w twoim przykładzie.

    To znaczy, że fakt, że A jest (pośrednio) pochodną object (podczas gdy poprawna), jest po prostu nieistotny. Istotne jest to, że A jest wartością struct Typ.

  2. tylko istniejąca konwersja, która pasuje do wzorca "Z typu wartości A do jego super - typu odniesieniaobject" jest klasyfikowany jako konwersja boksu. Tak więc każde przekształcenie z struct do object jest Z definicji uważane za Boks.

 0
Author: stakx,
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-02-23 03:40:17