Udostępnij za pośrednictwem


System.Delegate i delegate słowo kluczowe

Poprzednie

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 generowany przez kompilator podczas używania słowa kluczowego delegate mapuje się na wywołania metod wywołujące elementy członkowskie Delegate klas 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 tej klasy zgodną z System.Delegate użytym podpisem (w tym przypadku metoda zwracająca liczbę całkowitą i ma dwa argumenty). Typ tego delegata to Comparison. Typ delegata Comparison jest typem ogólnym. Aby uzyskać szczegółowe informacje na temat typów ogólnych, zobacz tutaj.

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

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ę dołączoną do delegata. Zmienną należy traktować jako nazwę metody i wywoływać ją przy użyciu normalnej składni wywołania metody.

Ten wiersz kodu sprawia, że niebezpieczne założenie: nie ma gwarancji, że element docelowy 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 przypisanie 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. Ta metoda może nie być 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 informuje kompilator, aby przekonwertować odwołanie metody na odwołanie, które może być używane jako obiekt docelowy wywołania delegata i dołączyć tę metodę jako obiekt docelowy wywołania.

Można było również jawnie zadeklarować zmienną typu Comparison<string> i wykonując 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 dla obiektów docelowych delegatów zostało omówione bardziej w dalszej sekcji.

Przykład Sort() zwykle dołącza pojedynczą metodę docelową 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 obsługę, z którą zwykle trzeba pracować z pełnomocnikami. Te funkcje są oparte na dwóch klasach w programie .NET Core Framework: Delegate i MulticastDelegate.

Klasa i jej pojedyncza System.Delegate podklasa System.MulticastDelegatebezpośrednia zapewniają obsługę platformy do tworzenia delegatów, rejestrowania metod jako obiektów docelowych delegatów i wywoływania wszystkich metod zarejestrowanych jako obiekt docelowy delegata.

Co ciekawe, System.Delegate klasy i System.MulticastDelegate nie są samymi typami delegatów. Zapewniają one podstawę dla wszystkich określonych typów delegatów. Ten sam proces projektowania języka nakazuje zadeklarowanie klasy pochodzącej z klasy 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. Oznaczało to, że delegaci zostali wywołani z odpowiednim typem i liczbą argumentów. I że każdy typ zwracany został poprawnie wskazany w czasie kompilacji. Delegaci byli częścią wersji 1.0 platformy .NET, która była wcześniejsza niż ogólne.

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 multiemisji oznacza, że można wywołać więcej niż jeden element docelowy metody podczas wywoływania za pośrednictwem delegata. Oryginalny projekt rozważał rozróżnienie między delegatami, w których można dołączyć i wywołać tylko jedną metodę docelową, oraz delegatów, w 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 i klasy, które obsługują delegaty, sprawdźmy, jak silnie typizowane delegaty są używane, tworzone i wywoływane.

Dalej