Wiem, że struktury w .NET nie obsługują dziedziczenia, ale nie jest do końca jasne, dlaczego są w ten sposób ograniczone.
Jaki powód techniczny uniemożliwia strukturom dziedziczenie po innych strukturach?
.net
inheritance
struct
Julia
źródło
źródło
Odpowiedzi:
Typy wartości nie mogą obsługiwać dziedziczenia, ponieważ są to tablice.
Problem polega na tym, że ze względów wydajnościowych i GC tablice typów wartości są przechowywane „w linii”. Na przykład,
new FooType[10] {...}
jeśliFooType
jest typem referencyjnym, na stercie zarządzanym zostanie utworzonych 11 obiektów (jeden dla tablicy i 10 dla każdego wystąpienia typu). JeśliFooType
jest zamiast tego typem wartości, tylko jedna instancja zostanie utworzona na zarządzanym stercie - dla samej tablicy (ponieważ każda wartość tablicy będzie przechowywana „w linii” z tablicą).Załóżmy teraz, że mamy dziedziczenie z typami wartości. W połączeniu z powyższym zachowaniem tablic w „pamięci wbudowanej”, zdarzają się złe rzeczy, co można zobaczyć w C ++ .
Rozważmy ten pseudo-C # kod:
Zgodnie z normalnymi regułami konwersji a
Derived[]
można zamienić na aBase[]
(na lepsze lub gorsze), więc jeśli użyjesz s / struct / class / g dla powyższego przykładu, skompiluje się i uruchomi zgodnie z oczekiwaniami, bez żadnych problemów. Ale jeśliBase
iDerived
są typami wartości, a tablice przechowują wartości w linii, to mamy problem.Mamy problem, ponieważ
Square()
nic oDerived
tym nie wiemy , użyje tylko arytmetyki wskaźników, aby uzyskać dostęp do każdego elementu tablicy, zwiększając o stałą wartość (sizeof(A)
). Zgromadzenie wyglądałoby mniej więcej tak:(Tak, to ohydny zestaw, ale chodzi o to, że będziemy zwiększać poprzez tablicę według znanych stałych czasu kompilacji, bez żadnej wiedzy, że używany jest typ pochodny).
Tak więc, gdyby tak się stało, mielibyśmy problemy z uszkodzeniem pamięci. W szczególności, w ramach
Square()
,values[1].A*=2
by rzeczywiście być modyfikującyvalues[0].B
!Spróbuj debugowania TEGO !
źródło
Wyobraź sobie struktury obsługujące dziedziczenie. Następnie oświadczam:
oznaczałoby to, że zmienne strukturalne nie mają stałego rozmiaru i dlatego mamy typy referencyjne.
Jeszcze lepiej, rozważ to:
źródło
Foo
dziedziczenia strukturyBar
nie powinno pozwalaćFoo
na przypisanie a do aBar
, ale zadeklarowanie struktury w ten sposób może pozwolić na kilka użytecznych efektów: (1) Utwórz specjalnie nazwany element członkowski typuBar
jako pierwszy elementFoo
iFoo
uwzględnij nazwy członków, że alias do tych członkówBar
, pozwalając kodu, które wykorzystywaneBar
być dostosowana do użyćFoo
zamiast tego, bez konieczności wymiany wszystkich odniesień dothing.BarMember
zthing.theBar.BarMember
, i zachowując możliwość odczytu i zapisu wszystkichBar
„s dziedzinach jak grupy; ...Struktury nie używają referencji (chyba że są w ramkach, ale powinieneś starać się tego unikać), dlatego polimorfizm nie ma znaczenia, ponieważ nie ma pośrednictwa przez wskaźnik odniesienia. Obiekty normalnie żyją na stercie i odwołują się do nich poprzez wskaźniki referencyjne, ale struktury są alokowane na stosie (chyba że są opakowane) lub są alokowane „wewnątrz” pamięci zajmowanej przez typ referencyjny na stercie.
źródło
Foo
który ma pole typu struktury,Bar
który mógłby traktowaćBar
elementy członkowskie jako własne, tak abyPoint3d
klasa mogła np hermetyzacjiPoint2d xy
ale odnoszą się doX
tej dziedzinie, jak teżxy.X
czyX
.Oto, co mówią dokumenty :
Zasadniczo mają one przechowywać proste dane i dlatego nie mają „dodatkowych funkcji”, takich jak dziedziczenie. Prawdopodobnie byłoby dla nich technicznie możliwe do obsługi jakiegoś ograniczonego rodzaju dziedziczenia (nie polimorfizmu, ponieważ są na stosie), ale uważam, że jest to również wybór projektu, aby nie obsługiwać dziedziczenia (jak wiele innych rzeczy w .NET języki są.)
Z drugiej strony zgadzam się z korzyściami płynącymi z dziedziczenia i myślę, że wszyscy osiągnęliśmy punkt, w którym chcemy, abyśmy
struct
odziedziczyli po kimś innym i zdaliśmy sobie sprawę, że to niemożliwe. Ale w tym momencie struktura danych jest prawdopodobnie tak zaawansowana, że i tak powinna być klasą.źródło
Point3D
z aPoint2D
; nie byłbyś w stanie aby użyć aPoint3D
zamiast aPoint2D
, ale nie musiałbyś ponownie implementowaćPoint3D
całkowicie od zera.) Tak i tak to zinterpretowałem ...class
sięstruct
w razie potrzeby.Dziedziczenie takie jak klasa nie jest możliwe, ponieważ struktura jest układana bezpośrednio na stosie. Dziedziczona struktura byłaby większa niż jej rodzic, ale JIT o tym nie wie i stara się umieścić zbyt dużo na zbyt małej przestrzeni. Brzmi trochę niejasno, napiszmy przykład:
Gdyby to było możliwe, zawiesiłoby się na następującym fragmencie:
Przestrzeń jest przeznaczona dla rozmiaru A, a nie dla rozmiaru B.
źródło
Jest jeden punkt, który chciałbym poprawić. Nawet jeśli powodem, dla którego struktur nie można dziedziczyć, jest to, że żyją na stosie, jest to właściwe, jest to jednocześnie w połowie poprawne wyjaśnienie. Struktury, jak każdy inny typ wartości, mogą znajdować się na stosie. Ponieważ będzie to zależeć od tego, gdzie zadeklarowano zmienną, będą one znajdować się na stosie lub w stercie . Dzieje się tak, gdy są to odpowiednio zmienne lokalne lub pola instancji.
Mówiąc to, Cecil Has a Name ujął to poprawnie.
Chciałbym to podkreślić, typy wartości mogą żyć na stosie. Nie oznacza to, że zawsze to robią. Zmienne lokalne, w tym parametry metody, will. Wszyscy inni nie. Niemniej jednak nadal pozostaje powodem, dla którego nie można ich odziedziczyć. :-)
źródło
Struktury są przydzielane na stosie. Oznacza to, że semantyka wartości jest prawie darmowa, a dostęp do elementów strukturalnych jest bardzo tani. To nie zapobiega polimorfizmowi.
Mogłabyś mieć każdą strukturę zaczynającą się od wskaźnika do swojej tablicy funkcji wirtualnych. Byłby to problem z wydajnością (każda struktura miałaby co najmniej rozmiar wskaźnika), ale jest to wykonalne. Pozwoliłoby to na funkcje wirtualne.
A co z dodawaniem pól?
Cóż, przydzielając strukturę na stosie, przydzielasz pewną ilość miejsca. Wymagane miejsce jest określane w czasie kompilacji (z wyprzedzeniem lub podczas JITtingu). Jeśli dodasz pola, a następnie przypiszesz do typu podstawowego:
Spowoduje to nadpisanie nieznanej części stosu.
Alternatywą jest dla środowiska wykonawczego, aby temu zapobiec, zapisując tylko sizeof (A) bajtów do dowolnej zmiennej A.
Co się stanie, jeśli B przesłoni metodę w A i odwołuje się do jej pola Integer2? Środowisko uruchomieniowe zgłasza MemberAccessException lub zamiast tego metoda uzyskuje dostęp do niektórych losowych danych na stosie. Żadne z nich nie jest dopuszczalne.
Dziedziczenie struktur jest całkowicie bezpieczne, o ile nie używasz struktur polimorficznie lub nie dodajesz pól podczas dziedziczenia. Ale nie są one zbyt przydatne.
źródło
Wydaje się, że to bardzo częste pytanie. Mam ochotę dodać, że typy wartości są przechowywane „w miejscu”, w którym deklarujesz zmienną; Oprócz szczegółów implementacji oznacza to, że nie ma nagłówka obiektu, który mówi coś o obiekcie, tylko zmienna wie, jakie dane tam się znajdują.
źródło
Struktury obsługują interfejsy, więc możesz w ten sposób robić pewne rzeczy polimorficzne.
źródło
IL jest językiem opartym na stosie, więc wywołanie metody z argumentem wygląda mniej więcej tak:
Po uruchomieniu metody zdejmuje ze stosu kilka bajtów, aby pobrać argument. Wie dokładnie ile bajtów ma wyskoczyć, ponieważ argument jest albo wskaźnikiem typu referencyjnego (zawsze 4 bajty na 32-bitach), albo typem wartości, dla którego rozmiar jest zawsze dokładnie znany.
Jeśli jest to wskaźnik typu referencyjnego, metoda wyszukuje obiekt w stercie i pobiera jego uchwyt typu, który wskazuje na tabelę metod, która obsługuje tę konkretną metodę dla tego dokładnego typu. Jeśli jest to typ wartości, nie jest konieczne wyszukiwanie w tabeli metod, ponieważ typy wartości nie obsługują dziedziczenia, więc istnieje tylko jedna możliwa kombinacja metoda / typ.
Gdyby typy wartości obsługiwały dziedziczenie, wystąpiłoby dodatkowe obciążenie w tym, że konkretny typ struktury musiałby zostać umieszczony na stosie, a także jego wartość, co oznaczałoby pewnego rodzaju wyszukiwanie w tabeli metod dla konkretnego konkretnego wystąpienia typu. Pozwoliłoby to wyeliminować zalety szybkości i wydajności typów wartości.
źródło