[ Pobierz całość w formacie PDF ]
tem.Collections.Generic, który trzeba jakoś rozwiązać:
using SysColGen = System.Collections.Generic;
using Microsoft.Internal.CrazyCollections;
class Foo
{
void Bar()
{
SysColGen.List list = new SysColGen.List();
// &
}
}
Typy specjalne
Na rysunku 2.1 zamieszczonym wcześniej w tym rozdziale pokazano kilka typów specjal-
nych wchodzących w skład hierarchii typów CTS. Są to delegacje, atrybuty niestandardowe
i wyliczenia. Każdy z nich oferuje rozszerzone mechanizmy systemu typów do pisania kodu
zarządzanego.
Delegacje
Delegacje to specjalne typy CTS, które reprezentują ściśle typizowane sygnatury metod.
Typy delegacyjne wywodzą się ze specjalnego typu System.Delegate, który z kolei wywodzi
się z System.ValueTySe. Delegacja może być utworzona i skonstruowana na dowolnej
Rozdział 2. Wspólny system typów 85
kombinacji metody i instancji, w której sygnatura metody odpowiada sygnaturze delegacji.
Delegacje można również tworzyć na metodach statycznych, a w takich przypadkach
instancja jest niepotrzebna. W CLR instancja delegacji jest odmianą ściśle typizowanego
wskaznika do funkcji.
Większość języków oferuje składnię upraszczającą tworzenie i konkretyzację delegacji. Na
przykład w C# nowy typ delegacyjny tworzy się za pomocą słowa kluczowego delegate:
public delegate void MyDelegate(int x, int y);
Instrukcja ta tworzy nowy typ delegacyjny o nazwie MyDelegate, który można konstruować
na metodach mających typ zwrotny void i przyjmujących dwa argumenty typu int. W VB
składnia jest podobna. Ukrywa ona wiele złożonych operacji, które musi wykonać kompilator
w celu wygenerowania rzeczywistych delegacji, dzięki czemu użytkownicy języka mają
ułatwione zadanie.
Naszą delegację można następnie skonstruować na docelowej metodzie, przekazywać,
a wreszcie wywołać. W języku C# wygląda to jak zwykłe wywołanie funkcji:
class Foo
{
void PrintPair(int a, int b)
{
Console.WriteLine("a = {0}", a);
Console.WriteLine("b = {0}", b);
}
void CreateAndInvoke()
{
// Odpowiednik instrukcji new MyDelegate(this.PrintPair):
MyDelegate del = PrintPair;
del(10, 20);
}
}
Metoda CreateAndInvoke konstruuje nową instancję MyDelegate na metodzie SrintSair
z celem w postaci bieżącego wskaznika this, po czym wywołuje ją.
Obsługa w CTS
Emitowany kod IL pokazuje złożoność obsługi delegacji w systemie typów:
struct MyDelegate : System.MulticastDelegate
{
public MyDelegate(object target, IntPtr methodPtr);
private object target;
private IntPtr methodPtr;
public internal void Invoke(int x, int y);
public internal System.IAsyncResult BeginInvoke(int x, int y,
System.IAsyncCallback callback, object state);
public internal void EndInvoke(System.IAsyncResult result);
}
86 Część I Podstawowe informacje o CLR
Konstruktor służy do formowania delegacji na docelowym obiekcie i wskazniku do funkcji.
Metody Invoke, BeginInvoke oraz EndInvoke implementują procedurę wywoływania delegacji
i są oznaczone jako internal (tzn. runtime w IL), aby wskazać, że implementację zapewnia
CLR; w IL ich ciało jest puste. Metoda Invoke wykonuje wywołanie synchroniczne, a Begi-
nInvoke oraz EndInvoke realizują wzorzec znany z modelu programowania asynchronicznego
(opisywanego dokładniej w rozdziale 10.), aby wykonać asynchroniczne wywołanie metody.
Zauważmy najpierw, że typ MyDelegate narusza jedną z omówionych wyżej zasad, miano-
wicie tę, że struktury nie mogą wywodzić się z typów innych niż ValueTySe. CTS zapewnia
specjalną obsługę delegacji, więc jest to dozwolone. Zwróćmy też uwagę, że MyDelegate
wywodzi się z MulticastDelegate; typ ten jest wspólną klasą bazową dla wszystkich delegacji
tworzonych w C# i obsługuje delegacje, które mają wiele celów. W punkcie poświęconym
zdarzeniom wyjaśnię, do czego to się przydaje. MulticastDelegate definiuje też wiele dodat-
kowych metod, które chwilowo zignorujemy i które pominięto w zamieszczonej wyżej dekla-
racji. Więcej informacji o tych metodach można znalezć w rozdziale 14.
Aby uformować delegację na docelowym obiekcie, IL używa konstruktora MyDelegate.
Konstruktor przyjmuje parametr wejściowy typu object, którym jest wskaznik this prze-
kazywany w momencie wywoływania metody (albo null w przypadku metod statycznych),
oraz IntStr reprezentujący zarządzany wskaznik do metody CLR. Jest to wskaznik do funkcji
w stylu C, który wskazuje kod skompilowany przez JIT w środowisku uruchomieniowym.
Sygnatura Invoke odpowiada sygnaturze zdefiniowanej delegacji, a CLR zapewnia zarówno
statycznie, jak i dynamicznie, że wskaznik do funkcji zawarty w delegacji pasuje do tej
sygnatury.
Pokazany wyżej kod CreateAndInvoke emituje poniższą sekwencję IL:
ldarg.0 // wczytuje wskaznik this na użytek metody CreateAndInvoke
ldftn void Foo::PrintPair(int32, int32)
newobj instance void MyDelegate::.ctor(object, native int)
ldc.i4.s 10
ldc.i4.s 20
callvirt instance void MyDelegate::Invoke(int32, int32)
ret
Zauważmy, że kod wczytuje wskaznik do metody SrintSair za pomocą instrukcji ldftn,
a następnie używa metody Invoke zdefiniowanej w MyDelegate, aby wywołać docelową metodę.
Powoduje to pośrednie wywołanie metody SrintSair z argumentami w postaci wartości 10 i 20.
Delegacje zostaną opisane znacznie bardziej szczegółowo w rozdziale 14.
Kowariancja i kontrawariancja
Reguły wiązania delegacji, które podałem wcześniej, są nieco uproszczone. Stwierdziłem, że
wartość zwrotna i typy parametrów docelowej metody muszą dokładnie odpowiadać delegacji,
aby można było uformować instancję delegacji na tej metodzie. Z technicznego punktu
widzenia nie jest to do końca prawdą. CLR 2.0 dopuszcza tzw. delegacje kowariancyjne
i kontrawariancyjne (choć wersje 1.x na to nie pozwalały). Terminy te są dobrze zakorzenio-
ne w informatyce i określają pewne postaci polimorfizmu w systemie typów. Kowariancja
oznacza, że można podstawić bardziej pochodny typ w miejscu, w którym oczekiwany jest
mniej pochodny typ. Kontrawariancja jest odwrotnością kowariancji oznacza, że można
podstawić mniej pochodny typ w miejscu, w którym oczekiwany jest bardziej pochodny typ.
Rozdział 2. Wspólny system typów 87
Wejście kowariancyjne jest dozwolone w kategoriach tego, co użytkownik może dostarczyć
metodzie. Jeśli metoda oczekuje klasy Bazowa, a ktoś dostarczy jej instancję klasy Sochodna
(wywodzącej się z Bazowa), środowisko uruchomieniowe na to zezwoli. Jest to standardowa
postać obiektowego polimorfizmu. Podobnie wyjście może być kontrawariancyjne w tym
sensie, że jeśli wywołujący oczekuje mniej pochodnego typu, można mu dostarczyć instan-
cję typu bardziej pochodnego.
Zagadnienia ko- i kontrawariancji są dość skomplikowane. W większości literatury można
przeczytać, że prawidłowe jest kontrawariancyjne wejście i kowariancyjne wyjście. Jest to
dokładna odwrotność tego, co napisałem powyżej! W literaturze stwierdzenie to odnosi
się zazwyczaj do możliwości przesłaniania metody za pomocą ko- lub kontrawariancyjnej
[ Pobierz całość w formacie PDF ]