To pytanie z perspektywy wewnętrznych kompilatorów.
Interesują mnie generyczne, a nie szablony (C ++), więc zaznaczyłem pytanie C #. Nie Java, ponieważ AFAIK generyczne w obu językach różnią się implementacjami.
Kiedy patrzę na języki bez generyków, jest to dość proste, możesz zweryfikować definicję klasy, dodać ją do hierarchii i to wszystko.
Ale co zrobić z klasą ogólną, a co ważniejsze, jak obsługiwać odniesienia do niej? Jak upewnić się, że pola statyczne są osobne dla poszczególnych instancji (tj. Za każdym razem, gdy rozstrzygane są parametry ogólne).
Powiedzmy, że widzę połączenie:
var x = new Foo<Bar>();
Czy dodam nową Foo_Bar
klasę do hierarchii?
Aktualizacja: do tej pory znalazłem tylko 2 odpowiednie posty, ale nawet one nie zawierają zbyt wielu szczegółów w sensie „jak to zrobić samodzielnie”:
Odpowiedzi:
Każda generalna instancja ma własną kopię (myląco nazwanej) MethodTable, w której przechowywane są pola statyczne.
Nie jestem pewien, czy warto myśleć o hierarchii klas jako o strukturze, która faktycznie istnieje w czasie wykonywania, jest bardziej logiczną konstrukcją.
Ale jeśli weźmiesz pod uwagę MethodTables, każda z pośrednim wskaźnikiem do swojej klasy podstawowej, do utworzenia tej hierarchii, to tak, to dodaje nową klasę do hierarchii.
źródło
Foo<string>
i nie wytwarzałyby z nich dwóch przypadków pola statycznegoFoo
.Widzę tam dwa konkretne pytania. Prawdopodobnie chcesz zadać dodatkowe powiązane pytania (jako osobne pytanie z linkiem do tego), aby uzyskać pełne zrozumienie.
W jaki sposób pola statyczne otrzymują osobne wystąpienia dla wystąpienia ogólnego?
Cóż, w przypadku elementów statycznych, które nie są powiązane z parametrami typu ogólnego, jest to dość łatwe (użyj słownika odwzorowanego z parametrów ogólnych na wartość).
Elementy (statyczne lub nie), które są powiązane z parametrami typu, mogą być obsługiwane przez kasowanie typu. Po prostu użyj najsilniejszego ograniczenia (często
System.Object
). Ponieważ informacje o typie są usuwane po sprawdzeniu typu kompilatora, oznacza to, że sprawdzanie typu środowiska wykonawczego nie będzie potrzebne (chociaż rzutowania interfejsu mogą nadal istnieć w czasie wykonywania).Czy każda ogólna instancja pojawia się osobno w hierarchii typów?
Nie w ogólnych .NET. Podjęto decyzję o wykluczeniu dziedziczenia z parametrów typu, więc okazuje się, że wszystkie wystąpienia typu ogólnego zajmują to samo miejsce w hierarchii typów.
Prawdopodobnie była to dobra decyzja, ponieważ brak wyszukania nazw z klasy podstawowej byłby niezwykle zaskakujący.
źródło
Foo<int>
iFoo<string>
uderzy te same dane zFoo
W / O ograniczeń.List<string>
iList<Form>
, następnie, ponieważList<T>
ma wewnętrznie członek rodzajuT[]
i nie ma żadnych ograniczeńT
, to co będziesz rzeczywiście dostać to kod maszynowy, który manipulujeobject[]
. Ponieważ jednak tylkoT
instancje są umieszczane w tablicy, wszystko, co wychodzi, może zostać zwrócone jakoT
bez dodatkowego sprawdzania typu. Z drugiej strony, gdybyś miałControlCollection<T> where T : Control
, wówczas tablica wewnętrznaT[]
stałaby sięControl[]
.Ogólnym sposobem w interfejsie kompilatora jest posiadanie dwóch rodzajów instancji typu, typ ogólny (
List<T>
) i związany typ ogólny (List<Foo>
). Typ ogólny określa, jakie funkcje istnieją, jakie pola i ma odwołania do typów ogólnych, gdziekolwiekT
są używane. Związany typ ogólny zawiera odwołanie do typu ogólnego i zestaw argumentów typu. Zawiera wystarczającą ilość informacji, aby wygenerować konkretny typ, zastępując ogólne odniesieniaFoo
do typu argumentami typu lub cokolwiek innego. Tego rodzaju rozróżnienie jest ważne, gdy dokonuje się wnioskowania typu i trzeba wnioskować wList<T>
porównaniu zList<Foo>
.Zamiast myśleć o rodzajach ogólnych, takich jak szablony (które bezpośrednio budują różne implementacje), pomocne może być myślenie o nich jak o konstruktorach typu funkcjonalnego języka (gdzie ogólne argumenty są jak argumenty funkcji, która daje ci typ).
Jeśli chodzi o zaplecze, tak naprawdę nie wiem. Cała moja praca z generics była ukierunkowana na CIL jako backend, więc mogłem tam skompilować je do obsługiwanych generics.
źródło
List<T>
zawiera on prawdziwy typ (jego definicję), podczas gdyList<Foo>
(dziękuję również za fragment terminologii) moim podejściem zachowuję deklaracjeList<T>
(oczywiście teraz obowiązująceFoo
zamiastT
).