W językach zorientowanych obiektowo, które obsługują ogólne parametry typu (znane również jako szablony klas i polimorfizm parametryczny, chociaż oczywiście każda nazwa ma inne konotacje), często możliwe jest określenie ograniczenia typu dla parametru typu, tak aby można było zejść z innego rodzaju. Na przykład jest to składnia w języku C #:
//for classes:
class ExampleClass<T> where T : I1 {
}
//for methods:
S ExampleMethod<S>(S value) where S : I2 {
...
}
Jakie są powody używania rzeczywistych typów interfejsów w porównaniu z typami ograniczonymi przez te interfejsy? Na przykład, jakie są przyczyny podpisania metody I2 ExampleMethod(I2 value)
?
object-oriented
type-systems
generics
GregRos
źródło
źródło
ref
parametrów typu wartości może faktycznie zmodyfikować typ wartości.Odpowiedzi:
Korzystanie z wersji parametrycznej daje
Jako przypadkowy przykład, załóżmy, że mamy metodę, która oblicza pierwiastki równania kwadratowego
A potem chcesz, aby działał na innych rodzajach, takich jak rzeczy poza
int
. Możesz napisać coś takiegoProblem polega na tym, że nie mówi to, co chcesz. To mówi
Nie możemy zrobić coś jak
int sol = solve(a, b, c)
gdybya
,b
ic
toint
dlatego, że nie wiemy, że metoda będzie zwracaćint
w końcu! Prowadzi to do niezręcznego tańca z upuszczaniem i modlitwą, jeśli chcemy zastosować rozwiązanie w szerszym znaczeniu.Wewnątrz funkcji ktoś mógłby podać nam liczbę zmiennoprzecinkową, bigint i stopnie, a my musielibyśmy je dodać i pomnożyć razem. Chcielibyśmy to statycznie odrzucić, ponieważ operacje między tymi 3 klasami będą bełkotliwe. Stopnie są mod 360, więc tak nie będzie
a.plus(b) = b.plus(a)
i podobne podobieństwa się pojawią.Jeśli zastosujemy polimorfizm parametryczny z podtypami, możemy wykluczyć to wszystko, ponieważ nasz typ faktycznie mówi, co mamy na myśli
Lub słowami „Jeśli podasz mi jakiś typ, który jest liczbą, mogę rozwiązać równania z tymi współczynnikami”.
To pojawia się również w wielu innych miejscach. Innym dobrym źródłem przykładów są funkcje, które streszczenie nad jakimś pojemniku, Ala
reverse
,sort
,map
, itd.źródło
Num<int>
) Jako dodatkowy argument. Zawsze możesz zaimplementować interfejs dla dowolnego typu poprzez delegację. Właśnie takie są klasy typu Haskell, z wyjątkiem o wiele bardziej żmudnego użytkowania, ponieważ trzeba jawnie ominąć interfejs.Ponieważ tego potrzebujesz ...
to dwa zdecydowanie różne podpisy. Pierwszy bierze dowolny typ implementujący interfejs, a jedyną gwarancją jest to, że zwracana wartość spełnia interfejs.
Drugi przyjmuje dowolny typ implementujący interfejs i gwarantuje, że zwróci co najmniej ten typ ponownie (zamiast czegoś, co spełnia mniej restrykcyjny interfejs).
Czasami potrzebujesz słabszej gwarancji. Czasami chcesz silniejszego.
źródło
Or
która pobiera dwaParser
obiekty (abstrakcyjna klasa bazowa, ale zasada obowiązuje) i zwraca nowąParser
(ale z innym typem). Użytkownik końcowy nie powinien wiedzieć ani przejmować się, jaki jest typ betonu.IEnumerable<T>
, zwraca inną,IEnumerable<T>
która jest np. W rzeczywistościOrderedEnumerable<T>
)Użycie ograniczonych danych ogólnych dla parametrów metody może pozwolić metodzie na bardzo zwrotny typ w zależności od tego, co przekazano. W .NET mogą mieć także dodatkowe zalety. Pomiędzy nimi:
Metodę, która przyjmuje ograniczoną wartość ogólną jako parametr
ref
lubout
, można przekazać zmienną spełniającą ograniczenie; z drugiej strony, metoda ogólna z parametrem typu interfejsu byłaby ograniczona do akceptowania zmiennych dokładnie tego typu interfejsu.Metoda z parametrem typu ogólnego T może akceptować ogólne zbiory T. Metoda, która akceptuje an,
IList<T> where T:IAnimal
będzie w stanie zaakceptowaćList<SiameseCat>
, ale metoda, która chciała anIList<Animal>
, nie będzie w stanie tego zrobić.Ograniczenie może czasami określać interfejs pod względem rodzaju ogólnego, np
where T:IComparable<T>
.Struktura, która implementuje interfejs, może być zachowana jako typ wartości, gdy zostanie przekazana do metody przyjmującej ograniczony parametr ogólny, ale musi zostać umieszczona w ramce, gdy zostanie przekazana jako typ interfejsu. Może to mieć ogromny wpływ na szybkość.
Ogólny parametr może mieć wiele ograniczeń, podczas gdy nie ma innego sposobu na określenie parametru „jakiegoś typu, który implementuje zarówno IFoo, jak i IBar”. Czasami może to być obosieczny miecz, ponieważ kod, który otrzymał parametr typu,
IFoo
będzie miał trudności z przekazaniem go do takiej metody, która spodziewałaby się podwójnego ograniczenia, nawet jeśli dana instancja spełniłaby wszystkie ograniczenia.Jeśli w konkretnej sytuacji nie byłoby żadnej korzyści z używania standardowego, po prostu zaakceptuj parametr typu interfejsu. Użycie generycznego zmusi system typów i JITtera do wykonania dodatkowej pracy, więc jeśli nie ma żadnych korzyści, nie należy tego robić. Z drugiej strony bardzo często stosuje się co najmniej jedną z powyższych zalet.
źródło