Mam trochę problemów ze zrozumieniem, jak użyłbym kowariancji i kontrawariancji w prawdziwym świecie.
Jak dotąd jedyne przykłady, które widziałem, to ten sam przykład starej tablicy.
object[] objectArray = new string[] { "string 1", "string 2" };
Byłoby miło zobaczyć przykład, który pozwoliłby mi go użyć podczas rozwoju, gdybym mógł zobaczyć, jak jest używany gdzie indziej.
c#
c#-4.0
covariance
Brzytwa
źródło
źródło
Odpowiedzi:
Powiedzmy, że masz klasę Person i klasę, która z niej pochodzi, Nauczycielu. Masz kilka operacji, które przyjmują
IEnumerable<Person>
argument jako argument. W swojej klasie School masz metodę, która zwracaIEnumerable<Teacher>
. Kowariancja umożliwia bezpośrednie użycie tego wyniku dla metod, które przyjmująIEnumerable<Person>
typ, zastępując typ bardziej pochodny typem mniej pochodnym (bardziej ogólnym). Kontrawariancja, wbrew intuicji, pozwala na użycie bardziej ogólnego typu, w którym określono bardziej pochodny typ.Zobacz także Kowariancja i kontrawariancja w produktach generycznych w witrynie MSDN .
Zajęcia :
Użycie :
źródło
Dla pełności…
źródło
void feed(IGobbler<Donkey> dg)
. Jeśli zamiast tego wziąłbyś IGobbler <Quadruped> jako parametr, nie mógłbyś przekazać smoka, który zjada tylko osły.Oto, co zebrałem, aby pomóc mi zrozumieć różnicę
tldr
źródło
Contravariance
przykładzie), kiedyFruit
jest rodzicemApple
?Słowa kluczowe in i out sterują regułami rzutowania kompilatora dla interfejsów i delegatów z parametrami ogólnymi:
źródło
Oto prosty przykład wykorzystujący hierarchię dziedziczenia.
Biorąc pod uwagę prostą hierarchię klas:
A w kodzie:
Niezmienność (tj. Parametry typu ogólnego * nie * ozdobione słowami kluczowymi
in
lubout
)Pozornie metoda taka jak ta
... powinien akceptować zbiór heterogeniczny: (co robi)
Jednak przekazanie kolekcji bardziej pochodnego typu kończy się niepowodzeniem!
Czemu? Ponieważ parametr ogólny
IList<LifeForm>
nie jest kowariantny -IList<T>
jest niezmienny, więcIList<LifeForm>
akceptuje tylko kolekcje (które implementują IList), w którychT
musi znajdować się sparametryzowany typLifeForm
.Jeśli implementacja metody
PrintLifeForms
była złośliwa (ale ma tę samą sygnaturę metody), powód, dla którego kompilator uniemożliwia przekazanie,List<Giraffe>
staje się oczywisty:Ponieważ
IList
zezwala na dodawanie lub usuwanie elementów, każda podklasa klasyLifeForm
mogłaby zostać dodana do parametrulifeForms
i naruszyłaby typ dowolnej kolekcji typów pochodnych przekazanych do metody. (W tym przypadku złośliwa metoda spróbuje dodaćZebra
dovar myGiraffes
). Na szczęście kompilator chroni nas przed tym niebezpieczeństwem.Kowariancja (ogólna z typem sparametryzowanym ozdobiona
out
)Kowariancja jest szeroko stosowana w niezmiennych kolekcjach (tj. Gdy nie można dodawać ani usuwać nowych elementów z kolekcji)
Rozwiązaniem powyższego przykładu jest zapewnienie, że używany jest kowariantny ogólny typ kolekcji, np.
IEnumerable
(Zdefiniowany jakoIEnumerable<out T>
).IEnumerable
nie ma metod zmiany kolekcji, a w wynikuout
kowariancji dowolny zbiór z podtypemLifeForm
może być teraz przekazany do metody:PrintLifeForms
można teraz nazywa sięZebras
,Giraffes
a każdyIEnumerable<>
z dowolnej podklasyLifeForm
Kontrawariancja (ogólna z parametryzowanym typem ozdobiona
in
)Kontrawariancja jest często używana, gdy funkcje są przekazywane jako parametry.
Oto przykład funkcji, która przyjmuje
Action<Zebra>
parametr jako parametr i wywołuje go na znanym wystąpieniu Zebry:Zgodnie z oczekiwaniami działa to dobrze:
Intuicyjnie to się nie powiedzie:
Jednak to się udaje
i nawet to się udaje:
Czemu? Ponieważ
Action
jest zdefiniowane jakoAction<in T>
, tj. Jestcontravariant
, co oznacza, że dlaAction<Zebra> myAction
, któremyAction
może wynosić „co najwyżej” aAction<Zebra>
, ale mniej pochodne nadklasyZebra
są również dopuszczalne.Chociaż na początku może to być nieintuicyjne (np. Jak można
Action<object>
przekazać parametr jako wymagającyAction<Zebra>
?), Jeśli rozpakujesz kroki, zauważysz, że sama wywoływana funkcja (PerformZebraAction
) jest odpowiedzialna za przekazywanie danych (w tym przypadkuZebra
instancja ) do funkcji - dane nie pochodzą z kodu wywołującego.Ze względu na odwrotne podejście polegające na używaniu funkcji wyższego rzędu w ten sposób, w momencie
Action
wywołania funkcji jest ona bardziej pochodnąZebra
instancją, która jest wywoływana względemzebraAction
funkcji (przekazywana jako parametr), chociaż sama funkcja używa mniej pochodnego typu.źródło
in
słowo kluczowe używane do określenia kontrawariancji ?Action<in T>
iFunc<in T, out TResult>
są kontrawariantne w typie danych wejściowych. (Moje przykłady wykorzystują istniejące typy niezmiennicze (List), kowariantne (IEnumerable) i kontrawariantne (Action, Func))C#
tego, nie wiedziałbym tego.Zasadniczo za każdym razem, gdy masz funkcję, która przyjmuje Enumerable jednego typu, nie możesz przekazać Enumerable typu pochodnego bez jawnego rzutowania.
Jednak tylko po to, aby ostrzec Cię przed pułapką:
To i tak okropny kod, ale istnieje, a zmieniające się zachowanie w C # 4 może wprowadzić subtelne i trudne do znalezienia błędy, jeśli użyjesz takiej konstrukcji.
źródło
Z MSDN
źródło
Sprzeczność
W prawdziwym świecie zawsze możesz skorzystać ze schroniska dla zwierząt zamiast schroniska dla królików, ponieważ za każdym razem, gdy schronisko jest gospodarzem królika, jest to zwierzę. Jeśli jednak zamiast schroniska dla zwierząt użyjesz schroniska dla królików, jego personel może zostać zjedzony przez tygrysa.
W kodzie, oznacza to, że jeśli masz
IShelter<Animal> animals
można po prostu napisaćIShelter<Rabbit> rabbits = animals
, jeśli obiecasz i zastosowanieT
wIShelter<T>
jedynie jako parametry metody tak:i zamień pozycję na bardziej ogólną, tj. zmniejsz wariancję lub wprowadź kontrawariancję.
Kowariancja
W prawdziwym świecie zawsze możesz skorzystać z dostawcy królików zamiast dostawcy zwierząt, ponieważ za każdym razem, gdy dostawca królika daje ci królika, jest to zwierzę. Jeśli jednak korzystasz z dostawcy zwierząt zamiast dostawcy królika, możesz zostać zjedzony przez tygrysa.
W kodzie, oznacza to, że jeśli masz
ISupply<Rabbit> rabbits
można po prostu napisaćISupply<Animal> animals = rabbits
, jeśli obiecasz i zastosowanieT
wISupply<T>
jedynie jako wartości zwracane metoda tak:i zamień element na bardziej pochodny, tj. zwiększ wariancję lub wprowadź ko- wariancję.
Podsumowując, jest to po prostu obietnica, którą możesz sprawdzić w czasie kompilacji , że będziesz traktować typ ogólny w określony sposób, aby zachować bezpieczeństwo typu i nie dać nikogo zjeść.
Możesz to przeczytać, aby podwójnie owinąć głowę wokół tego.
źródło
contravariance
jest interesujący. Czytam to jako wskazanie wymogu operacyjnego : bardziej ogólny typ musi obsługiwać przypadki użycia wszystkich typów wywodzących się z niego. Tak więc w tym przypadku schronisko dla zwierząt musi być w stanie zapewnić schronienie dla każdego rodzaju zwierząt. W takim przypadku dodanie nowej podklasy może spowodować przerwanie superklasy! To znaczy - jeśli dodamy podtyp Tyrannosaurus Rex , może to zrujnować nasze istniejące schronisko dla zwierząt .Delegat konwertera pomaga mi wizualizować obie koncepcje współpracujące ze sobą:
TOutput
reprezentuje kowariancję, w której metoda zwraca bardziej szczegółowy typ .TInput
reprezentuje kontrawariancję, w której metoda jest przekazywana do mniej określonego typu .źródło