Zanim przejdę do wyjaśnienia kilku usprawnień wprowadzonych w C# 4.0 spróbujmy zdefiniować pojęcia kowariancji oraz kontrawariancji. Te skomplikowane pojęcia odnoszą się po prostu do typów konwersji.
Kowariancja to określenie typu konwersji z bardziej specyficznego do bardziej ogólnego(klasy). Kontrawariancja to oczywiście przeciwieństwo (bazowa klasa do pochodnej). Jako przykład kowariancji w c# można pokazać np.:
object text = "Hello World!";
Deklaracja text jest kowariancją ponieważ możemy przypisać do zmiennej tylko takie same lub bardziej specyficzne typy (string jest subtypem object). Podobnie tablice są przykładem kowariancji –jesteśmy w stanie użyć następującej deklaracji:
object[] strings = new string[1];
Załóżmy, że mamy 3 klasy (classA, classB, classC):
class classA{} class classB:classA class classC:classB
Oraz następującą metodę:
public classB Method(classB parameter) { // logika }
Typ zwracany jest zawsze kowariancją. Możemy zatem wykorzystać takie konstrukcję:
classB b = Method(par); classA a = Methhod(par);
Kowariancja czyli konwersja z bardziej precyzyjnego (classA) do bardziej ogólnego(classB). Z kolei argumenty (parametry) są zawsze kontrawariancją:
Method(new classB()); Method(new classC());
Nie możemy przekazać jako parametr wartości bardziej ogólnej (classA). Mamy sytuacje odwrotną i jedynymi dozwolonymi wartościami są klasy bardziej specyficzne.
Typy generyczne w c# 3.0 były invariant (neutralne, niezmienne). Oznacza to, że nie może nastąpić żadna konwersja. Jeśli metoda zwraca IEnumarable<classB> wyłącznie takowy typ może zostać użyty:
private IEnumerable<classB> Method() { return null; } // IEnumerable<classA> listA = Method(); // Błąd - typy generyczne w c# 3.0 nie są kowariancją IEnumerable<classB> listB = Method(); // OK - invariant IEnumerable<classC> listC = Method(); // Błąd - typy generyczne w c# 3.0 nie są kontrawariancją
W c# 4.0 typy generyczne mogą jednak zostać zdefiniowane jako kowariancja. Interfejs IEnumarable jest przykładem kontrawariancji i jeśli utworzymy nowy projekt w VS 2010 w wersji c# 4.0 poniższy kod będzie całkowicie poprawny:
IEnumerable<classA> listA = Method(); // W wersji C# 4.0 poprawne! IEnumerable<classB> listB = Method();
Kowariancję w typach generycznych w c# 4.0 oznacza się słowem kluczowym out. Dlatego też, IEnumarable jest zdefiniowany następująco:
public interface IEnumerable<out T> // zamiast public interface IEnumerable<T>
Warto również wspomnieć, że tylko interfejsy oraz delegates mogą używać słowa kluczowego out.
To nie wszystko jednak… W c# 4.0 można również zdefiniować kontrawariancję za pomocą słowa in:
interface ICustomGenericType<in T> { }
Teraz poprawna stanie się następująca konstrukcja:
private ICustomGenericType<classB> Method() { return null; } ICustomGenericType<classB> listB = Method(); ICustomGenericType<classC> listC = Method();
Może komuś to się przyda a jeśli nie to przynajmniej będzie wiedział dlaczego pewien kod kompiluje się w c# 4.0 a w c# 3.0 już nie.
Warto wspomnieć że kowariancja i kontrawariancja jest możliwa w CLR od wersji 2.0.50727. Niestety dopiero kompilator 4.0 potrafi to wykorzystać.
Hej,
Bardzo fajny wpis.
Warto także wspomnieć, że IEnumerable w projektach Silverlight (v.4) jest nadal invariant.
Fajne, ostatnio trochę spierałem się z C# na ten temat, i teraz widzę, że po prostu miałem złe argumenty 🙂
“Interfejs IEnumarable jest przykładem kontrawariancji”
Tu jest błąd, bo IEnumarable jest przykładem kowariancji.
Zdecydwanie zgadzam się z przedmówcą. A że kilka miesięcy od publikacji komentarza minęło, pozwolę sobie przypomnieć w tej sprawie.