in i out - Kontrawariancja i kowariancja w języku C#

Kontrawariancja i kowariancja - typy precyzyjne a typy bardziej ogólne.


Pojęcia kontrawariancji i kowariancji w C# 4.0 są blisko powiązane z typami generycznymi.

Aby wyjaśnić pojęcia użyte zostaną następujące klasy:

class Pojazd{};
class Auto : Pojazd{};
class Audi : Auto{};

Kowariancja

Kowariancja jest to konwersja z bardziej precyzyjnego typu do bardziej ogólnego (a więc typem dla zwracanego obiektu może być zarówno typ tego obiektu jak i każdy obiekt bazowy). W C# wartość zwracana z funkcji jest kowariancją.

Kod prezentujący kowariancje:

class Pojazd { };
 
class Auto : Pojazd 
{
    static Auto GetAuto()
    {
        return new Auto();
    }
 
    Pojazd pojazd = GetAuto();
    Auto auto = GetAuto();
};
 
class Audi : Auto { };
A więc kowariancja pozwala do obiektu z klasy Pojazd przypisać obiekt typu Auto. Nie można jednak przypisać obiektu typu Audi, ponieważ nie zawsze obiekt zwracany musi być typu Audi.

Kontrawariancja

Kontrawariancja jest to przeciwieństwo kowariancji, czyli konwersja z typu bardziej ogólnego do typu bardziej precyzyjnego (obiekt może być typem takim jak argument lub każdym, który po nim dziedziczy). W C# kontrawariancją są parametry funkcji.

Kod prezentujący kontrawariancje:

public class Pojazd { };
 
public class Auto : Pojazd 
{
    public Auto Metoda(Auto auto)
    {
        return new Auto();
    }
 
    void Program()
    {
        Metoda(new Auto());
        Metoda(new Audi());
    }
};
 
public class Audi : Auto {    
};
A więc kontrawariancja pozwala na odwołanie się do bardziej precyzyjnego typu, czyli do parametru typu Auto możemy odwołać się typem dziedziczący po nim a więc Audi. Nie możemy natomiast odwołać się do typu nadrzędnego a więc do typu Pojazd, ponieważ nie każdy pojazd to samochód.

Udogodnienia jakie zostały wprowadzone w C# 4.0.

W C# 3.0 typy generyczne nie używały kowariancji ani kontrawariancji a więc mogły zwracać i przyjmować parametry tylko tego samego typu, dla jakiego zostały stworzone.

Przykład dla C# 3.0:

private IEnumerable<Auto> Metoda()
{
    return null;
}

IEnumerable<Pojazd> listaPojazd = Metoda(); // Błąd - typy generyczne nie są kowariancją w C# 3.0
IEnumerable<Auto> listaAuto = Metoda(); // OK 
IEnumerable<Audi> listaAudi = Metoda(); // Błąd - typy generyczne nie są kontrawariancją w C# 3.0

Przykład dla C# 4.0:

private IEnumerable<Auto> Metoda()
{
    return null;
}

IEnumerable<Pojazd> listaPojazd = Metoda(); // OK
IEnumerable<Auto> listaAuto = Metoda(); // OK 
Aby zapewnić obsługę kowariancji w C# 4.0 należy użyć słowa kluczowego out w deklaracji interfejsu.
public interface IEnumerable<out T>
Możliwe staje się wówczas przypisanie:
IEnumerable<Pojazd> listaPojazd = new Auto();
Natomiast, aby zdefiniować kontrawariancje należy użyć słowa kluczowego in w deklaracji interfejsu.
public interface IJakisInterfejs<in T>
Możliwe staje się wówczas przypisanie:
public IJakisInterfejs<Auto> Metoda()
    {
        return null;
    }

IJakisInterfejs<Auto> listaAut = Metoda();
IJakisInterfejs<Audi> listaAudi = Metoda();
W C# 3.0 była możliwość korzystania z kontra i kowariancji w zwykłych metodach oraz delegatach, jednak nie było możliwości korzystania w kowariancji oraz kontrawariancji przy użyciu typów generycznych.
Przy stosowaniu kowariancji nie należy mylić słowa kluczowego out z przekazywaniem parametru poprzez wyjście.

Więcej informacji na temat kontra- i kowariancji:

http://msdn.microsoft.com/pl-pl/library/ee207183.aspx
Komentarze facebook (polub nasz profil na FB aby je zobaczyć):