Uwaga
Dostęp do tej strony wymaga autoryzacji. Może spróbować zalogować się lub zmienić katalogi.
Dostęp do tej strony wymaga autoryzacji. Możesz spróbować zmienić katalogi.
System.Delegate i
W tym artykule opisano klasy na platformie .NET, które obsługują delegaty, oraz sposób mapowania tych klas na delegate
słowo kluczowe.
Definiowanie typów delegatów
Zacznijmy od słowa kluczowego "delegate", ponieważ jest to przede wszystkim to, czego będziesz używać podczas pracy z delegatami. Kod, który generuje kompilator podczas używania słowa kluczowego delegate
, zostanie zamapowany na wywołania metod, które uruchamiają elementy członkowskie klas Delegate i MulticastDelegate.
Typ delegata definiuje się przy użyciu składni podobnej do definiowania podpisu metody. Wystarczy dodać delegate
słowo kluczowe do definicji.
W naszym przykładzie użyjemy metody List.Sort(). Pierwszym krokiem jest utworzenie typu delegata porównania:
// From the .NET Core library
// Define the delegate type:
public delegate int Comparison<in T>(T left, T right);
Kompilator generuje klasę, pochodzącą z System.Delegate
, która jest zgodna z używanym podpisem (w tym przypadku metodą zwracającą liczbę całkowitą i mającą dwa argumenty). Typ tego delegata to Comparison
. Typ delegata Comparison
jest typem ogólnym. Aby uzyskać więcej informacji, zobacz Ogólne klasy i metody.
Zwróć uwagę, że składnia może wyglądać tak, jakby deklaruje zmienną, ale faktycznie deklaruje typ. Można zdefiniować typy delegatów wewnątrz klas, bezpośrednio wewnątrz przestrzeni nazw, a nawet w globalnej przestrzeni nazw.
Uwaga / Notatka
Deklarowanie typów delegatów (lub innych typów) bezpośrednio w globalnej przestrzeni nazw nie jest zalecane.
Kompilator generuje również programy obsługi dodawania i usuwania dla tego nowego typu, aby klienci tej klasy mogli dodawać i usuwać metody z listy wywołań wystąpienia. Kompilator wymusi, że sygnatura dodawanej lub usuniętej metody jest zgodna z sygnaturą używaną podczas deklarowania metody.
Deklarowanie wystąpień delegatów
Po zdefiniowaniu delegata można utworzyć wystąpienie tego typu. Podobnie jak wszystkie zmienne w języku C#, nie można deklarować wystąpień delegowanych bezpośrednio w przestrzeni nazw ani w globalnej przestrzeni nazw.
// inside a class definition:
// Declare an instance of that type:
public Comparison<T> comparator;
Typ zmiennej to Comparison<T>
, typ delegata zdefiniowany wcześniej. Nazwa zmiennej to comparator
.
Powyższy fragment kodu zadeklarował zmienną składową wewnątrz klasy. Można również zadeklarować zmienne delegowane, które są zmiennymi lokalnymi lub argumentami metod.
Wywoływanie delegatów
Metody, które znajdują się na liście wywołań delegata, są wywoływane przez wywołanie tego delegata.
Sort()
Wewnątrz metody kod wywoła metodę porównania, aby określić kolejność umieszczania obiektów:
int result = comparator(left, right);
W powyższym wierszu kod wywołuje metodę przypisaną delegatowi. Zmienną należy traktować jako nazwę metody i wywoływać ją przy użyciu normalnej składni wywołania metody.
Ten wiersz kodu opiera się na niebezpiecznym założeniu: nie ma gwarancji, że cel został dodany do delegata. Jeśli nie dołączono żadnych elementów docelowych, powyższy wiersz spowoduje zgłoszenie elementu NullReferenceException
. Idiomy używane do rozwiązania tego problemu są bardziej skomplikowane niż proste sprawdzanie wartości null i są omówione w dalszej części tej serii.
Przypisywanie, dodawanie i usuwanie obiektów docelowych wywołania
W ten sposób zdefiniowano typ delegata oraz sposób deklarowanego i wywoływanego wystąpień delegatów.
Deweloperzy, którzy chcą używać List.Sort()
metody, muszą zdefiniować metodę, której podpis jest zgodny z definicją typu delegata, i przypisać go do delegata używanego przez metodę sortowania. To zadanie dodaje metodę do listy wywołań tego obiektu delegata.
Załóżmy, że chcesz posortować listę ciągów według ich długości. Funkcja porównania może być następująca:
private static int CompareLength(string left, string right) =>
left.Length.CompareTo(right.Length);
Metoda jest zadeklarowana jako metoda prywatna. To dobrze. Być może nie chcesz, aby ta metoda była częścią interfejsu publicznego. Można go nadal używać jako metody porównania, gdy jest dołączony do delegata. Kod wywołujący będzie miał tę metodę dołączoną do listy docelowej obiektu delegata i może uzyskać do niej dostęp za pośrednictwem tego delegata.
Tę relację można utworzyć, przekazując tę metodę List.Sort()
do metody :
phrases.Sort(CompareLength);
Zwróć uwagę, że nazwa metody jest używana bez nawiasów. Użycie metody jako argumentu instruuje kompilator, aby przekonwertować odwołanie do metody na odwołanie, które może być użyte jako cel wywołania delegata, i ustawić tę metodę jako cel wywołania.
Można było również jawnie zadeklarować zmienną typu Comparison<string>
i wykonać przypisanie.
Comparison<string> comparer = CompareLength;
phrases.Sort(comparer);
W przypadku zastosowań, w których metoda używana jako obiekt docelowy delegata jest małą metodą, często używa się składni wyrażenia lambda do wykonywania przypisania:
Comparison<string> comparer = (left, right) => left.Length.CompareTo(right.Length);
phrases.Sort(comparer);
Używanie wyrażeń lambda jako obiektów docelowych delegatów jest omówione bardziej szczegółowo w dalszej sekcji.
Przykład Sort() zazwyczaj przypisuje jedną określoną metodę do delegata. Jednak obiekty delegowane obsługują listy wywołań, które mają wiele metod docelowych dołączonych do obiektu delegata.
Klasy Delegat i MulticastDelegate
Obsługa języka opisana powyżej udostępnia funkcje i wsparcie, z których zazwyczaj korzystasz podczas pracy z delegatami. Te funkcje są oparte na dwóch klasach w programie .NET Core Framework: Delegate i MulticastDelegate.
Klasa System.Delegate
i jej pojedyncza bezpośrednia podklasa System.MulticastDelegate
zapewniają wsparcie ramowe do tworzenia delegatów, rejestrowania metod jako cele delegatów oraz wywoływania wszystkich metod zarejestrowanych jako cele delegatów.
Co ciekawe, System.Delegate
i System.MulticastDelegate
klasy nie są same w sobie typami delegatów. Zapewniają one podstawę dla wszystkich określonych typów delegatów. Ten sam proces projektowania języka nakazuje, że nie można zadeklarować klasy pochodzącej z Delegate
lub MulticastDelegate
. Reguły języka C# uniemożliwiają jej.
Zamiast tego kompilator języka C# tworzy wystąpienia klasy pochodnej MulticastDelegate
podczas deklarowania typów delegatów za pomocą słowa kluczowego języka C#.
Ten projekt ma swoje korzenie w pierwszej wersji języków C# i .NET. Jednym z celów zespołu projektowego było zapewnienie, że język wymuszał bezpieczeństwo typu podczas korzystania z delegatów. To oznaczało zapewnienie, że delegaci zostali wywołani odpowiednim typem i liczbą argumentów. I że każdy typ zwracany został poprawnie wskazany w czasie kompilacji. Delegaty były częścią wersji 1.0 platformy .NET, przed wprowadzeniem generyków.
Najlepszym sposobem wymuszania bezpieczeństwa tego typu było utworzenie przez kompilator konkretnych klas delegatów, które reprezentowały używany podpis metody.
Mimo że nie można utworzyć klas pochodnych bezpośrednio, użyjesz metod zdefiniowanych w tych klasach. Przyjrzyjmy się najbardziej typowym metodom, które będą używane podczas pracy z delegatami.
Pierwszym, najważniejszym faktem, który należy pamiętać, jest to, że każdy delegat, z którym pracujesz, pochodzi od MulticastDelegate
. Delegat wielokrotny oznacza, że można wywołać więcej niż jedną metodę docelową podczas wywoływania przez delegata. Oryginalny projekt rozważał rozróżnienie między delegatami, do których można dołączyć i wywołać tylko jedną metodę docelową, a delegatami, do których można dołączyć i wywołać wiele metod docelowych. To rozróżnienie okazało się mniej przydatne w praktyce niż pierwotnie sądzono. Dwie różne klasy zostały już utworzone i zostały w strukturze od czasu jej początkowej publicznej wersji.
Metody, które będą najbardziej używane z delegatami, to Invoke()
i BeginInvoke()
/ EndInvoke()
.
Invoke()
wywoła wszystkie metody, które zostały dołączone do określonego wystąpienia delegata. Jak pokazano powyżej, zazwyczaj wywołujesz delegatów przy użyciu składni wywołania metody w zmiennej delegata. Jak zobaczysz w dalszej części tej serii, istnieją wzorce, które współpracują bezpośrednio z tymi metodami.
Teraz, gdy znasz już składnię języka, czyli klasy, które obsługują delegaty, zaczniemy sprawdzać, jak silnie typizowane delegaty są używane, tworzone i wywoływane.