Kowariancja

To pojęcie skomplikowane, świadczyć może o tym choćby jego nazwa. Na szczęście nie każdy z nas zobligowany jest do rozumienia tego typu zagadnień by być w stanie korzystać z jego dobrodziejstw. Jeśli chciałbyś sobie je przybliżyć – ten wpis jest właśnie dla Ciebie.

Temat rozłóżmy na czynniki pierwsze w iście akademickim stylu: niezrozumiałą definicja, przykład i do domu. Zapraszam do lektury.

Kowariancja

Jeśli typ A można przekonwertować na typ B, to X ma kowariantny parametry typu, jeśli X<A> można przekonwertować na X<B>.

Aby lepiej zobrazować powyższą definicję, spójrz na poniższy kod.

Implementujemy generyczny stos:

public class Stack<T>
{
    int position;
    T[] data = new T[100];

    public void Push(T obj) => data[position++] = obj;

    public T Pop() => data[--position];
    }
}

Dołóżmy do tego trzy proste klasy i trochę dziedziczenia:

public class Animal
{
}

public class Bird : Animal
{
}

public class Cat : Animal
{
}

Spróbujmy stworzyć stos ptaków i stos zwierząt który będzie mieć referencję do wcześniej stworzonego stosu ptaków.

Stack birds = new Stack(); //wszystko w porządeczku
Stack animals = birds; //na całe szczęście błąd!

Błąd wynika z bezpieczeństwa statycznych typów, nie są one automatycznie wariantne. Dzięki temu nie uda nam się operacja w której moglibyśmy chcieć do stosu ptaków dorzucić kota (co skończyło by się krwawą rzezią).

Można sprawić, by powyższy kod stał się poprawnym, aby tego dokonać konieczne jest implementowanie przez stos interfejsu (z kowariantnym parametrem typu).

Załóżmy, że napisaliśmy sobie taki interfejs, a nasz stos go implementuje – o tak:

public interface IPoppable
{
    T Pop();
}

public class Stack : IPoppable
{
    int position;
    T[] data = new T[100];

    public void Push(T obj) => data[position++] = obj;

    public T Pop() => data[--position];
}

Czym to skutkuje?

Słowo kluczowe out zawarte w deklaracji interfejsu mówi, że:

T może być używany na pozycjach WYJŚCIOWYCH, oznacza parametr T jako kowariantny

dzięki czemu możemy dokonać takich deklaracji:

var birds = new Stack();
birds.Push(new Bird());

IPoppable animals = bears; //bez błędu

Dozwolona jest konwersja z birds na animals, ponieważ parametr typu T jest kowariantny. Mając na uwadze typy jest to operacja bezpieczna. Nie uda nam się wpuścić kota do stosu ptaków jako, że nie możemy wprowadzać takich obiektów (kot) do interfejsu, w którym parametr T może występować tylko na pozycjach wyjściowych.

Jeśli poszukujesz większej ilości ciekawych treści na pewno zainteresują Cię inne wpisy, które umieściłem na blogu. Zapraszam do pozostawienia komentarzy, czekam na Twoją opinię, uwagi i feedback.

Pozdrawiam Cię serdecznie i życzę fantastycznego dnia!
Wojtek