Wiem, że Java implementuje polimorfizm parametryczny (Generics) z wymazywaniem. Rozumiem, czym jest wymazywanie.
Wiem, że C # implementuje polimorfizm parametryczny z reifikacją. Wiem, że możesz sprawić, że będziesz pisać
public void dosomething(List<String> input) {}
public void dosomething(List<Int> input) {}
lub że możesz wiedzieć w czasie wykonywania, jaki jest parametr typu sparametryzowanego typu, ale nie rozumiem, co to jest .
- Co to jest typ zreifikowany?
- Co to jest wartość zreifikowana?
- Co się dzieje, gdy typ / wartość jest reifikowany?
c#
generics
reification
Martijn
źródło
źródło
if
acja jest procesem przekształcaniaswitch
konstrukcji z powrotem wif
/else
, podczas gdy poprzednio została ona przekształcona z anif
/else
na aswitch
...Odpowiedzi:
Reifikacja to proces podejmowania abstrakcyjnej rzeczy i tworzenia konkretnej rzeczy.
Termin reifikacja w C # rodzajach ogólnych odnosi się do procesu, w którym definicja typu ogólnego i jeden lub więcej argumentów typu ogólnego (rzecz abstrakcyjna) są łączone, aby utworzyć nowy typ ogólny (konkretna rzecz).
Do wyrażenia to inaczej, jest to proces podejmowania definicji
List<T>
orazint
i produkcji konkretnyList<int>
typ.Aby lepiej to zrozumieć, porównaj następujące podejścia:
W języku generycznym języka Java definicja typu ogólnego jest przekształcana zasadniczo w jeden konkretny typ ogólny współużytkowany przez wszystkie dozwolone kombinacje argumentów typów. W ten sposób wiele typów (na poziomie kodu źródłowego) jest mapowanych na jeden typ (na poziomie binarnym) - ale w rezultacie informacje o argumentach typu instancji są odrzucane w tej instancji (usuwanie typu) .
W C # typach ogólnych definicja typu ogólnego jest przechowywana w pamięci w czasie wykonywania. Zawsze, gdy wymagany jest nowy konkretny typ, środowisko uruchomieniowe łączy definicję typu ogólnego i argumenty typu i tworzy nowy typ (reifikacja). Więc otrzymujemy nowy typ dla każdej kombinacji argumentów typu w czasie wykonywania .
System.Type
klasy (nawet jeśli konkretna kombinacja argumentów typu ogólnego, którą tworzysz, nie działa t pojawiają się bezpośrednio w kodzie źródłowym).W szablonach C ++ definicja szablonu jest przechowywana w pamięci w czasie kompilacji. Za każdym razem, gdy w kodzie źródłowym wymagane jest nowe wystąpienie typu szablonu, kompilator łączy definicję szablonu i argumenty szablonu i tworzy nowy typ. Otrzymujemy więc unikalny typ dla każdej kombinacji argumentów szablonu w czasie kompilacji .
źródło
Reifikacja oznacza ogólnie (poza informatyką) „uczynienie czegoś prawdziwym”.
W programowaniu coś jest reifikowane, jeśli jesteśmy w stanie uzyskać dostęp do informacji o tym w samym języku.
W przypadku dwóch całkowicie niezwiązanych z typami ogólnymi przykładów czegoś, co C # robi i nie zostało zreifikowane, weźmy metody i dostęp do pamięci.
Języki OO na ogół mają metody (i wiele, które nie mają funkcji, które są podobne, ale nie są związane z klasą). W związku z tym możesz zdefiniować metodę w takim języku, wywołać ją, być może nadpisać i tak dalej. Nie wszystkie takie języki pozwalają na traktowanie samej metody jako danych do programu. C # (a tak naprawdę .NET zamiast C #) pozwala na użycie
MethodInfo
obiektów reprezentujących metody, więc w C # metody są reifikowane. Metody w języku C # to „obiekty pierwszej klasy”.Wszystkie języki praktyczne mają sposoby na dostęp do pamięci komputera. W języku niskiego poziomu, takim jak C, możemy zajmować się bezpośrednio mapowaniem między adresami numerycznymi używanymi przez komputer, więc takie podejście
int* ptr = (int*) 0xA000000; *ptr = 42;
jest rozsądne (o ile mamy dobry powód, by podejrzewać, że uzyskanie dostępu do adresu pamięci0xA000000
w ten sposób wygrywa '' coś wysadzić). W C # nie jest to rozsądne (możemy to wymusić w .NET, ale przy zarządzaniu pamięcią .NET przenosząc rzeczy, jest mało prawdopodobne, że będzie przydatne). C # nie ma zreifikowanych adresów pamięci.Tak więc, ponieważ refied oznacza „ urzeczywistniony ”, „reifikowany typ” jest typem, o którym możemy „rozmawiać” w danym języku.
W przypadku leków generycznych oznacza to dwie rzeczy.
Jednym z nich jest to, że
List<string>
jest to typ taki, jaki jeststring
lubint
jest. Możemy porównać ten typ, poznać jego nazwę i zapytać o to:Konsekwencją tego jest to, że możemy „mówić” o typach parametrów metody ogólnej (lub metody klasy ogólnej) w samej metodzie:
Z reguły robienie tego zbyt często jest „śmierdzące”, ale ma wiele przydatnych przypadków. Na przykład spójrz na:
Nie powoduje to wielu porównań między typem
TSource
i różnymi typami dla różnych zachowań (zazwyczaj znak, że w ogóle nie powinieneś używać typów ogólnych), ale dzieli się między ścieżką kodu dla typów, które mogą byćnull
(powinny zostać zwrócone,null
jeśli nie znaleziono elementu i nie wolno dokonywać porównań w celu znalezienia minimum, jeśli jeden z porównywanych elementów tonull
) oraz ścieżkę kodu dla typów, których nie możnanull
(powinna zostać wyrzucona, jeśli nie znaleziono elementu, i nie trzeba się martwić o możliwośćnull
elementów ).Ponieważ
TSource
jest to „rzeczywiste” w metodzie, to porównanie można wykonać w czasie wykonywania lub w czasie jittingu (ogólnie w czasie jittingu, z pewnością powyższy przypadek robiłby to w czasie jittingu i nie generowałby kodu maszynowego dla ścieżki, która nie została wybrana) i mamy oddzielna „rzeczywista” wersja metody dla każdego przypadku. (Chociaż w ramach optymalizacji kod maszynowy jest współdzielony dla różnych metod dla różnych parametrów typu referencyjnego, ponieważ może to nie wpływać na to, a zatem możemy zmniejszyć ilość jitted kodu maszynowego).(Nie jest powszechne mówienie o reifikacji typów ogólnych w C #, chyba że masz do czynienia również z Javą, ponieważ w C # po prostu przyjmujemy tę reifikację za pewnik; wszystkie typy są reifikowane. W Javie typy nieogólne są określane jako reifikowane, ponieważ to to rozróżnienie między nimi a typami rodzajowymi).
źródło
Min
powyżej jest przydatne? W inny sposób bardzo trudno jest spełnić udokumentowane zachowanie.Enumerable.Min<TSource>
jest inne, ponieważ nie zgłasza się dla typów bez odwołań do pustej kolekcji, ale zwraca wartość domyślną (TSource) i jest udokumentowane tylko jako „Zwraca minimalną wartość w sekwencji ogólnej”. Twierdziłbym, że oba powinny wrzucić do pustej kolekcji lub że element „zero” powinien zostać przekazany jako linia bazowa, a komparator / funkcja porównania powinna być zawsze przekazywana)Jak już zauważył duffymo , „reifikacja” nie jest kluczową różnicą.
W Javie generyczne są w zasadzie po to, aby ulepszyć obsługę kompilacji - pozwala na użycie silnie wpisanych, np. Kolekcji w kodzie, i zapewnia bezpieczeństwo typów. Jednak istnieje to tylko w czasie kompilacji - skompilowany kod bajtowy nie ma już pojęcia o rodzajach ogólnych; wszystkie typy ogólne są przekształcane w typy „konkretne” (przy użyciu,
object
jeśli typ ogólny jest nieograniczony), dodając w razie potrzeby konwersje i sprawdzanie typów.W .NET typy ogólne są integralną funkcją środowiska CLR. Kiedy kompilujesz typ ogólny, pozostaje on ogólny w wygenerowanym IL. Nie jest po prostu przekształcany w kod nieogólny, jak w Javie.
Ma to wpływ na praktyczne działanie leków generycznych. Na przykład:
SomeType<?>
umożliwiać przekazanie dowolnej konkretnej implementacji danego typu generycznego. C # nie może tego zrobić - każdy określony ( zreifikowany ) typ ogólny jest własnym typem.object
. Może to mieć wpływ na wydajność w przypadku używania typów wartości w takich rodzajach ogólnych. W C #, gdy używasz typu wartości w typie ogólnym, pozostaje on typem wartości.Aby dać przykład, załóżmy, że masz
List
typ ogólny z jednym argumentem ogólnym. W JavieList<String>
iList<Int>
ostatecznie będą dokładnie tego samego typu w czasie wykonywania - typy ogólne istnieją naprawdę tylko dla kodu w czasie kompilacji. Wszystkie wywołania np.GetValue
Zostaną przekształcone odpowiednio na(String)GetValue
i(Int)GetValue
.W C #
List<string>
iList<int>
są to dwa różne typy. Nie są wymienne, a ich bezpieczeństwo typu jest również wymuszane w czasie wykonywania. Bez względu na to, co robisz,new List<int>().Add("SomeString")
będzie nigdy pracy - podstawowa przechowywanie wList<int>
to naprawdę jakiś tablica całkowitą, natomiast w Javie, to koniecznieobject
tablicą. W C # nie ma żadnych rzutów, żadnego boksu itp.Powinno to również wyjaśnić, dlaczego C # nie może zrobić tego samego, co Java
SomeType<?>
. W Javie wszystkie typy generyczne „wywodzące się z”SomeType<?>
są dokładnie tego samego typu. W języku C # wszystkie różne określoneSomeType<T>
typy są odrębnymi typami. Usuwając kontrole w czasie kompilacji, można przejśćSomeType<Int>
zamiastSomeType<String>
(i tak naprawdę wszystko toSomeType<?>
oznacza "zignoruj sprawdzenia w czasie kompilacji dla danego typu ogólnego"). W C # nie jest to możliwe, nawet w przypadku typów pochodnych (to znaczy, że nie można tego zrobić,List<object> list = (List<object>)new List<string>();
mimo żestring
pochodzi zobject
).Obie implementacje mają swoje wady i zalety. Było kilka razy, kiedy chciałbym móc po prostu zezwolić
SomeType<?>
jako argument w C # - ale po prostu nie ma sensu, w jaki sposób działają C # generics.źródło
List<>
,Dictionary<,>
i tak dalej w C #, ale różnica między tym a danej listy betonowej lub słownika zajmuje sporo refleksji na moście. Wariancja na interfejsach pomaga w niektórych przypadkach, w których kiedyś chcieliśmy łatwo wypełnić tę lukę, ale nie we wszystkich.List<>
do utworzenia wystąpienia nowego określonego typu ogólnego - ale nadal oznacza to utworzenie konkretnego typu, który chcesz. Ale nie możeszList<>
na przykład użyć go jako argumentu. Ale tak, przynajmniej pozwala to wypełnić lukę za pomocą odbicia.T
może spełnić ograniczenie typu miejsca przechowywania,U
to sytuacja, w którejT
iU
są tego samego typu, lubU
typ, który może zawierać odwołanie do wystąpieniaT
. Nie byłoby możliwe posiadanie znaczącego miejsca przechowywania typu,SomeType<?>
ale teoretycznie byłoby możliwe istnienie ogólnego ograniczenia tego typu.Reifikacja jest koncepcją modelowania zorientowanego obiektowo.
Reify to czasownik oznaczający „ urealnić coś abstrakcyjnego” .
Podczas programowania obiektowego często modeluje się obiekty świata rzeczywistego jako komponenty oprogramowania (np. Okno, przycisk, osoba, bank, pojazd itp.)
Powszechne jest również przekształcanie abstrakcyjnych pojęć w komponenty (np. WindowListener, Broker itp.)
źródło