Natknąłem się na nie w podręczniku, który czytam na C #, ale mam trudności ze zrozumieniem ich, prawdopodobnie z powodu braku kontekstu.
Czy istnieje dobre, zwięzłe wyjaśnienie, czym one są i do czego są przydatne?
Edytuj dla wyjaśnienia:
Kowariantny interfejs:
interface IBibble<out T>
.
.
Interfejs kontrawariantny:
interface IBibble<in T>
.
.
c#
.net
interface
covariance
contravariance
NibblyPig
źródło
źródło
Odpowiedzi:
Za pomocą
<out T>
można traktować odwołanie do interfejsu jako jedno w górę w hierarchii.Za pomocą
<in T>
można traktować odwołanie do interfejsu jako jeden w dół w hierarchii.Spróbuję wyjaśnić to bardziej po angielsku.
Powiedzmy, że pobierasz listę zwierząt ze swojego zoo i zamierzasz je przetworzyć. Wszystkie zwierzęta (w twoim zoo) mają imię i unikalny identyfikator. Niektóre zwierzęta to ssaki, niektóre to gady, niektóre to płazy, niektóre to ryby itd., Ale wszystkie to zwierzęta.
Tak więc, korzystając z listy zwierząt (która zawiera zwierzęta różnych typów), możesz powiedzieć, że wszystkie zwierzęta mają imiona, więc oczywiście byłoby bezpiecznie uzyskać nazwy wszystkich zwierząt.
A co jeśli masz tylko listę ryb, ale musisz traktować je jak zwierzęta, czy to działa? Intuicyjnie powinno działać, ale w C # 3.0 i wcześniejszych ten fragment kodu nie będzie się kompilował:
IEnumerable<Animal> animals = GetFishes(); // returns IEnumerable<Fish>
Powodem tego jest to, że kompilator nie „wie”, co zamierzasz lub nie możesz zrobić z kolekcją zwierząt po jej pobraniu. Z tego, co wie, może istnieć sposób
IEnumerable<T>
na ponowne umieszczenie obiektu na liście, co potencjalnie pozwoliłoby na umieszczenie zwierzęcia, które nie jest rybą, w kolekcji, która powinna zawierać tylko ryby.Innymi słowy, kompilator nie może zagwarantować, że jest to niedozwolone:
animals.Add(new Mammal("Zebra"));
Więc kompilator po prostu odmawia skompilowania twojego kodu. To jest kowariancja.
Spójrzmy na kontrawariancję.
Ponieważ nasze zoo może obsłużyć wszystkie zwierzęta, z pewnością poradzi sobie z rybami, więc spróbujmy dodać trochę ryb do naszego zoo.
W C # 3.0 i wcześniej nie kompiluje się to:
List<Fish> fishes = GetAccessToFishes(); // for some reason, returns List<Animal> fishes.Add(new Fish("Guppy"));
W tym przypadku kompilator mógłby zezwolić na ten fragment kodu, mimo że metoda zwraca
List<Animal>
po prostu dlatego, że wszystkie ryby są zwierzętami, więc jeśli zmieniliśmy typy na takie:List<Animal> fishes = GetAccessToFishes(); fishes.Add(new Fish("Guppy"));
Wtedy to zadziałałoby, ale kompilator nie może stwierdzić, że nie próbujesz tego zrobić:
List<Fish> fishes = GetAccessToFishes(); // for some reason, returns List<Animal> Fish firstFist = fishes[0];
Ponieważ lista jest w rzeczywistości listą zwierząt, jest to niedozwolone.
Zatem przeciwwariancja i współwariancja to sposób, w jaki traktujesz odniesienia do obiektów i co możesz z nimi robić.
in
Iout
słowa kluczowe w C # 4.0 konkretnie znaki interfejs do jednego lub drugiego. Dziękiin
, możesz umieścić typ ogólny (zwykle T) w pozycjach wejściowych , co oznacza argumenty metody i właściwości tylko do zapisu.W przypadku
out
można umieścić typ ogólny w pozycjach wyjściowych , które są wartościami zwracanymi metod, właściwościami tylko do odczytu i parametrami wyjściowymi metody.Pozwoli ci to zrobić to, co zamierzasz zrobić z kodem:
IEnumerable<Animal> animals = GetFishes(); // returns IEnumerable<Fish> // since we can only get animals *out* of the collection, every fish is an animal // so this is safe
List<T>
ma zarówno kierunek do wewnątrz, jak i na zewnątrz na T, więc nie jest to ani kowariancja, ani przeciwwariancja, ale interfejs, który pozwala na dodawanie obiektów, takich jak ten:interface IWriteOnlyList<in T> { void Add(T value); }
pozwoli ci to zrobić:
IWriteOnlyList<Fish> fishes = GetWriteAccessToAnimals(); // still returns IWriteOnlyList<Animal> fishes.Add(new Fish("Guppy")); <-- this is now safe
Oto kilka filmów przedstawiających koncepcje:
Oto przykład:
namespace SO2719954 { class Base { } class Descendant : Base { } interface IBibbleOut<out T> { } interface IBibbleIn<in T> { } class Program { static void Main(string[] args) { // We can do this since every Descendant is also a Base // and there is no chance we can put Base objects into // the returned object, since T is "out" // We can not, however, put Base objects into b, since all // Base objects might not be Descendant. IBibbleOut<Base> b = GetOutDescendant(); // We can do this since every Descendant is also a Base // and we can now put Descendant objects into Base // We can not, however, retrieve Descendant objects out // of d, since all Base objects might not be Descendant IBibbleIn<Descendant> d = GetInBase(); } static IBibbleOut<Descendant> GetOutDescendant() { return null; } static IBibbleIn<Base> GetInBase() { return null; } } }
Bez tych znaków można by skompilować:
public List<Descendant> GetDescendants() ... List<Base> bases = GetDescendants(); bases.Add(new Base()); <-- uh-oh, we try to add a Base to a Descendant
albo to:
public List<Base> GetBases() ... List<Descendant> descendants = GetBases(); <-- uh-oh, we try to treat all Bases as Descendants
źródło
Ten post jest najlepszym, jaki przeczytałem na ten temat
Krótko mówiąc, kowariancja / kontrawariancja / niezmienność zajmuje się automatycznym rzutowaniem typów (od podstawowego do wyprowadzonego i odwrotnie). Rzuty tego typu są możliwe tylko wtedy, gdy przestrzegane są pewne gwarancje dotyczące operacji odczytu / zapisu wykonywanych na rzutowanych obiektach. Przeczytaj post, aby uzyskać więcej informacji.
źródło