2011-01-12 10 views
40

Czy ktoś może podać mi proste przykłady C# z convariance, contravariance, invariance i contra-invariance (jeśli coś takiego istnieje).Proste przykłady koegzystencji i kontrawariancji

Wszystkie próbki, które widziałem do tej pory, po prostu rzucają jakiś obiekt w System.Object.

+0

Ktoś dał oddające obiekt do 'System.Object' jako przykład kowariancji? To wcale nie jest w porządku. –

+0

Uznałem, że to oznacza przekazanie jakiegoś obiektu (z konkretnym, bardziej wyprowadzonym typem) w miejsce 'System.Object', niekoniecznie rzutując' object' na 'System.Object', co byłoby bezcelowe. –

+0

_Variance_ nie należy mylić z _casting_, to nie to samo. Zobacz: [Różnica między kowariancją a upcastingiem] (http://stackoverflow.com/a/6707697/949681). – Pressacco

Odpowiedz

84

mógłby ktoś podać mi proste C# przykłady convariance, kontrawariancji, niezmienności i przeciwwskazania niezmienności (jeśli coś takiego istnieje).

Nie mam pojęcia, co oznacza "przeciw-niezmienniczość". Reszta jest łatwa.

Oto przykład kowariancji:

void FeedTheAnimals(IEnumerable<Animal> animals) 
{ 
    foreach(Animal animal in animals) 
     animal.Feed(); 
} 
... 
List<Giraffe> giraffes = ...; 
FeedTheAnimals(giraffes); 

Interfejs IEnumerable<T> jest kowariantna. Fakt, że Giraffe można zamienić na Animal, oznacza, że ​​IEnumerable<Giraffe> można zamienić na IEnumerable<Animal>. Od List<Giraffe> implementuje IEnumerable<Giraffe> ten kod kończy się pomyślnie w C# 4; nie powiodłoby się w C# 3, ponieważ kowariancja na IEnumerable<T> nie działała w C# 3.

To powinno mieć sens. Ciąg żyraf może być traktowany jako sekwencja zwierząt.

Oto przykład z kontrawariancji:

void DoSomethingToAFrog(Action<Frog> action, Frog frog) 
{ 
    action(frog); 
} 
... 
Action<Animal> feed = animal=>{animal.Feed();} 
DoSomethingToAFrog(feed, new Frog()); 

Action<T> delegat jest kontrawariantny. Fakt, że Frog można zamienić na Animal, oznacza, że ​​Action<Animal> można zamienić na Action<Frog>. Zauważ, jak ta relacja jest przeciwna w kierunku kowariantnego; dlatego jest to wariant "contra". Z powodu wymienialności ten kod się powiódł; to by się nie udało w C# 3.

To powinno mieć sens. Akcja może przyjąć dowolne Zwierzę; potrzebujemy akcji, która może przyjąć dowolną Żabę, a akcja, która może przyjąć każde Zwierzę, z pewnością może wziąć dowolną Żabę.

Przykładem niezmienności:

void ReadAndWrite(IList<Mammal> mammals) 
{ 
    Mammal mammal = mammals[0]; 
    mammals[0] = new Tiger(); 
} 

możemy zdać IList<Giraffe> do tej rzeczy? Nie, ponieważ ktoś ma zamiar napisać do niego Tygrysa, a tygrys nie może być na liście żyraf. Czy możemy przekazać w tej sprawie IList<Animal>? Nie, ponieważ będziemy czytać z tego Ssaka, a lista Zwierząt może zawierać Żabę. IList<T> jest niezmienna. Można go używać tylko tak, jak jest w rzeczywistości.

Aby uzyskać dodatkowe informacje na temat projektu tej funkcji, zapoznaj się z moją serią artykułów na temat tego, jak ją zaprojektowaliśmy i zbudowaliśmy.

http://blogs.msdn.com/b/ericlippert/archive/tags/covariance+and+contravariance/

+2

Warto na przykład wskazać w swoim ostatnim przykładzie, że mógłbyś * rzucić 'Giraffe []' na 'Mammal []' i przekazać to, co spowodowałoby błąd w czasie wykonywania. –

+3

@Dan: Dobra uwaga. C# obsługuje "zepsutą" kowariancję tablicową, gdzie pewne kowariantne konwersje są dozwolone, nawet jeśli mogą powodować awarie w czasie wykonywania. –

+0

@Eric: Tak, zdecydowanie rozumiem to rozumowanie (myślę): często deweloperzy oczekują "podzbioru" interfejsu 'T []' (lub 'IList '), aby uzyskać dostęp do elementów przez indeks bez intencji pisania do kolekcji. Dlatego osobiście wierzę dość mocno, że powinien istnieć coś w rodzaju kowariantnego interfejsu 'IArray ' z indeksowanym geterem w BCL. Pozwoliłoby to, aby 'List ' działał jak 'IList ', gdy jest używany tylko do dostępu indeksowanego, a nie jego właściwości zmiennych (tj. W tym samym przypadku, w którym 'Giraffe []' może bezpiecznie działać jako 'Mammal [] '). –

3

Niezmienniczość (w tym kontekście) to brak zarówno ko-i kontraii wariancji. Zatem przeciw-niezmienniczość nie ma sensu. Dowolny parametr, który nie jest oznaczony jako in lub out, jest niezmienny. Oznacza to, że ten typ parametru może być zarówno zużywany, jak i zwracany.

Dobrym przykładem współzmienności jest IEnumerable<out T>, ponieważ oczywiście IEnumerable<Derived> można zastąpić IEnumerable<Base>. Lub Func<out T>, który zwraca wartości typu T.
Na przykład IEnumerable<Dog> można przekonwertować na IEnumerable<Animal>, ponieważ każdy pies jest zwierzęciem.

W przypadku contra-wariancji można użyć dowolnego interfejsu użytkownika lub delegata. IComparer<in T> lub Action<in T> przyszło mi do głowy. Te nigdy nie zwracają zmiennej typu T, tylko ją przyjmują. I gdzie ty oprócz otrzymania Base możesz przekazać Derived.

Myślenie o nich jako o parametrach typu "tylko wejście" lub "tylko wyjście" ułatwia zrozumienie IMO.

A słowo Invariants zazwyczaj nie jest używane razem z wariancją typu, ale z niezmiennikami klasy lub metody i reprezentuje właściwość zachowaną. Zobacz this stackover thread, gdzie omówiono różnice między niezmiennikami i niezmiennością.

2

Jeśli weźmiesz pod uwagę regularne użycie generycznych, regularnie używasz interfejsu do obsługi obiektu, ale obiekt jest instancją klasy - nie możesz utworzyć instancji interfejsu. Użyj prostej listy ciągów jako przykładu.

IList<string> strlist = new List<string>(); 

Jestem pewien, że jesteś świadoma zalet stosując IList<> zamiast bezpośrednio za pomocą List<>. Pozwala to na inwersję kontroli i możesz zdecydować, że nie chcesz dłużej używać List<>, ale zamiast tego potrzebujesz LinkedList<>. Powyższy kod działa poprawnie, ponieważ ogólny typ interfejsu i klasy jest taki sam: string.

Może to być nieco bardziej skomplikowane, jeśli chcesz utworzyć listę ciągów znaków. Rozważmy następujący przykład:

IList<IList<string>> strlists = new List<List<string>>(); 

To wyraźnie nie będzie kompilować, ponieważ ogólne typy argumentów IList<string> i List<string> nie są takie same. Nawet jeśli zadeklarowałbyś zewnętrzną listę jako zwykłą klasę, taką jak List<IList<string>>, nie skompilowałaby się - argumenty typu nie są zgodne.

A oto, gdzie może pomóc kowariancja. Kowariancja pozwala na użycie bardziej pochodnego typu jako argumentu typu w tym wyrażeniu. Jeśli zmieniono by IList<> na kowariantną, to wystarczyłoby skompilować i naprawić problem.Niestety, nie jest kowariantna IList<>, ale jeden interfejsy Rozciąga się:

IEnumerable<IList<string>> strlists = new List<List<string>>(); 

Ten kod kompiluje teraz, argumenty typu są takie same jak były wyżej.