Kowariancja i kontrawariancja a C# 4.0 oraz typy generyczne

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ż nieUśmiech.

6 thoughts on “Kowariancja i kontrawariancja a C# 4.0 oraz typy generyczne”

  1. Warto wspomnieć że kowariancja i kontrawariancja jest możliwa w CLR od wersji 2.0.50727. Niestety dopiero kompilator 4.0 potrafi to wykorzystać.

  2. Fajne, ostatnio trochę spierałem się z C# na ten temat, i teraz widzę, że po prostu miałem złe argumenty 🙂

  3. “Interfejs IEnumarable jest przykładem kontrawariancji”
    Tu jest błąd, bo IEnumarable jest przykładem kowariancji.

  4. Zdecydwanie zgadzam się z przedmówcą. A że kilka miesięcy od publikacji komentarza minęło, pozwolę sobie przypomnieć w tej sprawie.

Leave a Reply

Your email address will not be published.