2010-04-27 11 views
69

Natrafiłem na te w podręczniku, który czytam na C#, ale mam trudności z ich zrozumieniem, prawdopodobnie z powodu braku kontekstu.Zrozumienie interfejsów kowariantnych i contravariant w C#

Czy istnieje zwięzłe zwięzłe wyjaśnienie, czym one są i jakie są przydatne?

Edycja dla wyjaśnienia:

kowariantna interfejs:

interface IBibble<out T> 
. 
. 

kontrawariantny interfejs:

interface IBibble<in T> 
. 
. 
+0

Może być przydatny: [Blog Post] (http://weblogs.asp.net/paulomorgado/archive/2010/04/13/c-4-0-covariance-and-contravariance-in-generics.aspx) – Krunal

+2

To jest krótkie i dobre wydarzenie IMHO: http://blogs.msdn.com/csharpfaq/archive/2010/02/16/covariance-and-contravariance-faq.aspx – digEmAll

+0

Hmm jest dobre, ale nie wyjaśnij _why_, co tak naprawdę mnie niepokoi. – NibblyPig

Odpowiedz

113

Z <out T> można traktować jako jedno odniesienie interfejsu w górę w hierarchii.

Dzięki <in T> można traktować odniesienie do interfejsu jako jedno w dół w hiearchacie.

Pozwólcie, że spróbuję wyjaśnić to w bardziej angielskich terminach.

Załóżmy, ż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 itp., Ale wszystkie są zwierzętami.

Tak więc, ze swoją listą zwierząt (która zawiera zwierzęta różnych typów), można powiedzieć, że wszystkie zwierzęta mają nazwę, więc oczywiście można bezpiecznie uzyskać nazwę wszystkich zwierząt.

Co jednak, jeśli masz tylko listę ryb, ale musisz traktować je jak zwierzęta, czy to działa? Intuicyjnie, to powinno działać, ale w C# 3.0 i wcześniej, ten fragment kodu nie kompilacji:

IEnumerable<Animal> animals = GetFishes(); // returns IEnumerable<Fish> 

Powodem tego jest to, że kompilator nie „wie”, co zamierzasz, lub może , zrób kolekcję zwierząt po jej odzyskaniu. Z tego, co wie, może istnieć sposób, aby IEnumerable<T> umieścić obiekt z powrotem na liście, a to 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 nie jest to dozwolone: ​​

animals.Add(new Mammal("Zebra")); 

więc kompilator po prostu wręcz odmawia skompilować kod. To jest kowariancja.

Spójrzmy na sprzeczność.

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 języku C# 3.0 i wcześniej, to nie skompilować:

List<Fish> fishes = GetAccessToFishes(); // for some reason, returns List<Animal> 
fishes.Add(new Fish("Guppy")); 

Tutaj kompilator mógłby pozwalają ten kawałek kodu, choć metoda zwraca List<Animal> po prostu dlatego, że wszystkie ryby są zwierzętami, więc jeśli tylko zmienił typy do to:

List<Animal> fishes = GetAccessToFishes(); 
fishes.Add(new Fish("Guppy")); 

Wtedy to działa, ale kompilator nie może określić, że nie próbujesz to zrobić:

List<Fish> fishes = GetAccessToFishes(); // for some reason, returns List<Animal> 
Fish firstFist = fishes[0]; 

Ponieważ lista jest w rzeczywistości listą zwierząt, nie jest to dozwolone.

Tak więc przeciwieństwo i współzmienność to sposób traktowania odniesień do obiektów i tego, co wolno z nimi robić.

Słowa kluczowe w C# 4.0 wyraźnie oznaczają interfejs jako jeden lub drugi. Z in, możesz umieścić typ ogólny (zwykle T) w wejściu -pozycje, co oznacza argumenty metody i właściwości tylko do zapisu.

Z out, masz prawo do umieszczenia typu rodzajowego w wyjście -positions, czyli wartości Sposób powrotu, tylko do odczytu właściwości i parametry się metoda.

To pozwoli Ci zrobić to, co zamierza 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 wewnątrz i na zewnątrz na T-kierunkach, więc nie jest to ani CO ani contra wariant wariant, ale interfejs, który pozwolił na dodawanie obiektów, takich jak to:

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, który pokazuje pojęcia:

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, następujące mógł skompilować:

public List<Descendant> GetDescendants() ... 
List<Base> bases = GetDescendants(); 
bases.Add(new Base()); <-- uh-oh, we try to add a Base to a Descendant 

lub to:

public List<Base> GetBases() ... 
List<Descendant> descendants = GetBases(); <-- uh-oh, we try to treat all Bases 
               as Descendants 
+0

Hmm, czy byłbyś w stanie wyjaśnić cel kowariancji i kontrawariancji? To może mi pomóc lepiej to zrozumieć. – NibblyPig

+1

Zobacz ostatni bit, którym wcześniej zapobiegał kompilator, celem wejścia i wyjścia jest określenie, co można zrobić z bezpiecznymi interfejsami (lub typami), aby kompilator nie uniemożliwił bezpiecznego wykonywania rzeczy. –

+0

Doskonała odpowiedź, obejrzałem filmy, które bardzo im pomogły, i połączyłem je z twoim przykładem, posortowałem to teraz. Pozostaje tylko jedno pytanie i dlatego wymagane są "out" i "in", dlaczego studio visual automatycznie nie wie, co próbujesz zrobić (lub co z tego wynika)? – NibblyPig

4

This post jest najlepszym czytałem na ten temat

W skrócie, kowariancji/kontrawariancji/niezmienności zajmuje się automatycznym odlewaniem (od podstawy do pochodnej i na odwrót).Odlewania tego typu są możliwe tylko wtedy, gdy pewne gwarancje są przestrzegane pod względem czynności odczytu/zapisu wykonywanych na rzutowanych obiektach. Przeczytaj wpis, aby uzyskać więcej informacji.

+0

bardzo dobry artykuł. :) dzięki – gsimoes

+4

Link wydaje się martwy. Oto zarchiwizowana wersja: https://web.archive.org/web/20140626123445/http://adamnathan.co.uk/?p=75 – si618