Home > .NET 4.0, C# > [PL] .NET 4.0 – What’s new – Kowariancja i kontrawariancja w typach generycznych#3

[PL] .NET 4.0 – What’s new – Kowariancja i kontrawariancja w typach generycznych#3

January 24, 2010 Leave a comment Go to comments

Czas zacząć przedstawiać nowy element, który znalazł się w C# 4.0. Tym razem jest to kowariancja i kontrawariancja, która dostępna jest w typach generycznych. Czym jest kowariancja i kontrawariancja znaleźć można na angielskiej wikipedii, ale postaram się przybliżyć na czym to polega w tym wpisie.

Przed przystąpieniem do omówienia zagadnienia przygotujmy parę prostych klas. Utworzymy sobie dwie klasy. Pierwszą o nazwie klient oraz drugą o nazwie VIP dziedziczącą po kliencie.

Bazując na naszej wiedzy przypomnijmy sobie czym są typy generyczne w praktyce, dostępne dotychczas od C# 2.0. Nie będę tutaj przytaczał czym jest programowanie generyczne, ale przedstawię jedynie parę przykładów, od których zaczniemy. Mamy przykładowo dwie listy zawierające każda z nich osobno klientów oraz VIPów.

IEnumerable<Client> clientList = new List<Client>();

IEnumerable<VIP> vipList = new List<VIP>();

Interfejs generyczny IEnumerable<T> pozwala nam na operowanie na wszelkiego rodzaju kolekcjach, w tym na listach. Czasami może zajść potrzeba, że chcemy, aby w clientList znaleźli się klienci z vipList (zamiast tworzyć kilka różnych list w programie, chcemy mieć jedną zmienną, w której będzie znajdowała się albo lista klientów, albo lista VIPów). Intuicja nam podpowiada, że VIP dziedziczy po Client, więc powinniśmy łatwo podstawić zmienną

clientList = vipList;

ale Visual Studio 2008 pokazuje nam błąd

CastProblemI tu pytanie, jak zrzutować w miarę łatwo, bez używania dodatkowych metod listę VIPów na listę Client. Nie ma prostej metody ?! Jest to znana bolączka występująca do .NET 3.5 dla typów generycznych i delegatów.

Począwszy od C# 4.0 została wprowadzona kowariancja

w typach generycznych  i w Visual Studio 2010 błąd taki jak wyżej się nie pojawi. Pytanie czemu? Jak jest to rozróżniane? Wystarczy spojrzeć już na samą definicję interfejsu IEnumerable w C# 3.5

public interface IEnumerable<T> : IEnumerable

i porównać ją z tą samą definicją dostępną w C# 4.0

public interface IEnumerable<out T> : IEnumerable

jak widać pojawił się dodatkowy parametr out informujący o możliwości kowariancji typu generycznego. Przyjrzyjmy się mu bliżej na innym, stworzonym przykładzie. Jest chyba to najlepszy przykład użycia kowariancji z wykorzystaniem interfejsu generycznego. W skrócie, mamy możliwość przypisania

IEnumerable<Client> clientList = new List<VIP>();

 

Kowariancja i kontrawariancja w delegatach dostępna była już od wersji 3.0, jednak nie było to możliwe w delegatach generycznych. Utworzymy sobie dwa delegaty generyczne w Visual Studio 2010. Pierwszy tak jak dotychczas w C# 3.5, a drugi używając dodatkowo parametru out.

delegate T FunctionA<T>();

delegate T FunctionB<out T>();

Następnie przypisujemy do pierwszego delegata FunctionA określoną prostą funkcję zwracającą określony obiekt, klienta lub VIPa (dla uproszczenia napisałem to używając składni Lambda, ale możliwe jest też przypisanie jakiejś funkcji zwracającej dany typ), np. public VIP GetVip() { return new VIP(); }.

FunctionA<VIP> vipFunctionA = () => new VIP();

FunctionA<Client> clientFunctionA = () => new Client();

Czasami może zajść potrzeba, że chcemy, aby delegat clientFunctionA wskazywał na metodę tworzącą VIPów (bo przecież możemy to zrobić, bo w hierarchii klas Client jest wyżej niż VIP). Tak jak w przykładzie na MSDN (Kowariancja i kontrawariancja w delegatach) możemy do zmiennej typu HandlerMethod wskazać FirstHandler lub SecondHandler (który dziedziczy po FirstHandler), ale nie jest tak w przypadku delegatów generycznych. Wskazując

clientFunctionA = vipFunctionA;

dostajemy tak jak w przykładzie z listami błąd konwersji typów. Tworząc analogicznie zmienne i typy przetrzymujące wskaźnik na funkcję, ale z wykorzystaniem delegatu FunctionB (wyposażonego w nasze słówko out) w C# 4.0:

FunctionB<VIP> vipFunctionB = () => new VIP();

FunctionB<Client> clientFunctionB = () => new Client();

możemy już łatwo utworzyć takie przypisanie, bo wskazaliśmy, że istnieje możliwość przypisania do delegatu obiektu z klasy dziedziczącej.

clientFunctionB = vipFunctionB;

Oczywiście w drugą stronę tego nie zrobimy, bo nie możemy zwrócić obiektu VIP przy pomocy metody zwracającej Client będącej wyżej w hierarchii. Pojęcie kowariancji jest łatwe do zrozumienia, problemy mogą się pojawić przy kontrawariancji.

Czym jest kontrawariancja?

Nasuwa się od razu odpowiedź – przeciwieństwem kowariancji, po części jest to prawda, ale patrząc na poprzedni przykład, to w jaki sposób możemy przy pomocy metody zwracającej obiekt Client przypisać do obiektu typu VIP, bo przecież Client może nie mieć wielu metod, które VIP używa. Kontrawariancja w typach generycznych ma zupełnie inne zastosowanie. Wyobraźmy sobie, że mamy metodę wykonującą jakąś akcję, np. sprawdzenie klienta. Nasz system posiada jednak wiele metod sprawdzających różnych typów (np. Firmy) które osobno są obsługiwane. Aby w pewien sposób usprawnić sobie życie utworzony został delegat opisujący funkcję sprawdzającą w naszym przypadku jedynie klienta (klasa bazowa Client). Aby sprawdzić zachowanie i zrozumieć lepiej kontrawariancje utworzymy sobie dwa delegaty jak poniżej. Pierwszy z nich dostępny w C# do wersji 3.5, a drugi w C# w wersji 4.0:

delegate void ActionA<T>(T t);

delegate void ActionB<in T>(T t);

Przyjrzyjmy się najpierw ActionA. Utworzymy sobie dwie zmienne ActionA, ale dla różnych typów:

ActionA<Client> clientActionA = (c) => { Console.WriteLine("AC:" + c); };

ActionA<VIP> vipActionA = (c) => { Console.WriteLine("AV:" + c); };

Jeżeli nasza funkcja sprawdzająca VIP działa tak samo jak zwykłego klienta, a rozróżnianie znajduje się w samej funkcji, lub akcja dla kienta i VIPa jest identyczna, to możemy przecież łatwo zrobić coś takiego:

vipActionA = clientActionA;

Czyli wskazać, że VIPa sprawdzać ma ta sama metoda co klienta. Ale tutaj pojawia się znowu znany nam błąd ‘Cannot implicitly convert’, pozostaje więc nam utworzyć dwie osobne metody, tak jak powyżej. W C# 4.0 dodany został jednak parameter in, informujący, że do parametru metody może zostać przekazany obiekt będący w hierarchii wyżej. Sprawdźmy jak wygląda to w przypadku użycia ActionB:

ActionB<Client> clientActionB = (c) => { Console.WriteLine("BC:" + c); };

ActionB<VIP> vipActionB = (c) => { Console.WriteLine("BV:" + c); };

Jeżeli vipActionB ma wykonywać te same czynności co clientActionB, to możemy łatwo wskazać, że

vipActionB = clientActionB;

Kontrawariancja przez delegaty generyczne pozwala nam na przekazanie przez parametr obiektu, który jest wyżej w hierarchii niż obiekt parametru. Wykorzystywać to możemy np. w obsłudze wyjątków.

Po co nam dodatkowe parametry in i out ?

Typy generyczne z założenia definiują nam pewną strukturę bazową (template), który może być wykorzystywany przy implementacji klasy z już określonym typem. Implementując swój interfejs generyczny i wykorzystując w jakiejś klasie z danym typem T określamy, że tylko ten typ T może zostać wykorzystany. Parametry in i out, wskazują nam, że implementując interfejs i wykorzystując dalej w programie możemy nieco ograniczyć tą restrykcję pozwalając na użycie danej klasy z typem T na użycie klas, które są w określonej hierarchii z tym typem T. Kowariancja jest używana bardziej w parametrach wyjściowych funkcji, a kontrawariancja w parametrach wejściowych funkcji. Przykładem może być delegat Func(T, TResult) wykorzystujący kowariancję i kontrawariancję.  Listę interfejsów i delegatów generycznych dostępnych w .NET 4.0 wykorzystujących kowariancję i kontrawariancję można znaleźć na stronie MSDN [5].

Linki i artykuły

[1] Kowariancja i kontrawariancja w delegatach: http://msdn.microsoft.com/en-us/library/ms173174(VS.80).aspx

[2] Kowariancja i kontrawariancja w informatyce: http://en.wikipedia.org/wiki/Covariance_and_contravariance_%28computer_science%29 

[3] Kowariancja i kontrawariancja w C# 4.0: http://blogs.msdn.com/charlie/archive/2008/10/28/linq-farm-covariance-and-contravariance-in-visual-studio-2010.aspx 

[4] Typy generyczne w .NET: http://msdn.microsoft.com/en-us/library/ms172192.aspx 

[5] Kowariancja i kontrawariancja w typach generycznych w .NET 4.0: http://msdn.microsoft.com/en-us/library/dd799517(VS.100).aspx

  1. Marcin
    January 28, 2012 at 10:13

    zajebisty wpis!

  1. No trackbacks yet.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: