Nuta
Dostęp do tej strony wymaga autoryzacji. Możesz 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.
Co to są delegaty?
Delegata można traktować jako sposób przechowywania odwołania do metody, podobnie jak przechowuje się odwołanie do obiektu. Podobnie jak w przypadku przekazywania obiektów do metod, można przekazać odwołania do metod przy użyciu delegatów. Jest to przydatne, gdy chcesz napisać elastyczny kod, w którym różne metody mogą być "podłączone", aby zapewnić różne zachowania.
Załóżmy na przykład, że masz kalkulator, który może wykonywać operacje na dwóch liczbach. Zamiast twardego kodowania dodawania, odejmowania, mnożenia i dzielenia w oddzielnych metodach, można by użyć delegatów do reprezentowania dowolnej operacji, która przyjmuje dwie liczby i zwraca wynik.
Definiowanie typów delegatów
Teraz zobaczmy, jak utworzyć typy delegatów przy użyciu słowa kluczowego delegate . Podczas definiowania typu delegata zasadniczo tworzysz szablon opisujący, jakiego rodzaju metody można przechowywać w tym delegatu.
Typ delegata definiuje się przy użyciu składni podobnej do podpisu metody, ale z delegate słowem kluczowym na początku:
// Define a simple delegate that can point to methods taking two integers and returning an integer
public delegate int Calculator(int x, int y);
Ten Calculator delegat może przechowywać odwołania do dowolnej metody, która przyjmuje dwa int parametry i zwraca wartość int.
Przyjrzyjmy się bardziej praktycznym przykładom. Jeśli chcesz posortować listę, musisz poinformować algorytm sortowania, jak porównać elementy. Zobaczmy, jak delegaty pomagają w metodzie List.Sort() . Pierwszym krokiem jest utworzenie typu delegata dla operacji porównania:
// From the .NET Core library
public delegate int Comparison<in T>(T left, T right);
Ten Comparison<T> delegat może przechowywać odwołania do dowolnej metody, która:
- Przyjmuje dwa parametry typu
T - Zwraca wartość
int(zazwyczaj -1, 0 lub 1, aby wskazać "mniejsze niż", "równe" lub "większe niż")
Podczas definiowania typu delegata w ten sposób, kompilator automatycznie generuje klasę pochodną od System.Delegate, która pasuje do twojego podpisu. Ta klasa zarządza całą złożonością przechowywania i wywoływania odwołań do metod.
Typ delegata Comparison jest typem ogólnym, co oznacza, że może pracować z dowolnym typem T. Aby uzyskać więcej informacji na temat typów ogólnych, zobacz Ogólne klasy i metody.
Zwróć uwagę, że mimo że składnia wygląda podobnie do deklarowania zmiennej, deklarujesz nowy 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 wymusza, że podpis dodawanej lub usuwanej metody jest zgodny z podpisem używanym podczas deklarowania typu delegata.
Deklarowanie wystąpień delegatów
Po zdefiniowaniu typu delegata można utworzyć wystąpienia (zmienne) tego typu. Pomyśl o tym, tworząc "miejsce", w którym można przechowywać odwołanie do metody.
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:
public Comparison<T> comparator;
Typ tej zmiennej to Comparison<T> (zdefiniowany wcześniej typ delegata), a nazwa zmiennej to comparator. W tym momencie comparator nie wskazuje jeszcze żadnej metody — jest to jak puste miejsce oczekujące na wypełnienie.
Zmienne delegowane można również zadeklarować jako zmienne lokalne lub parametry metody, podobnie jak w przypadku dowolnego innego typu zmiennej.
Wywoływanie delegatów
Po utworzeniu wystąpienia delegata wskazującego metodę można wywołać tę metodę za pośrednictwem delegata. Metody z listy wywołań delegata można wywołać, traktując delegata tak, jakby był metodą.
Oto, jak metoda Sort() używa delegata porównania do określenia kolejności obiektów:
int result = comparator(left, right);
W tym wierszu kod wywołuje metodę przypisaną do delegata. Zmienną delegata traktujesz tak, jakby była nazwą metody i wywołujesz ją przy użyciu normalnej składni wywołania metody.
Jednak ten wiersz kodu czyni niebezpiecznym założenie, że do delegata została dodana metoda docelowa. Jeśli nie dołączono żadnych metod, powyższy wiersz spowoduje zgłoszenie elementu NullReferenceException . Wzorce używane do rozwiązania tego problemu są bardziej zaawansowane 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
Teraz wiesz, jak definiować typy delegatów, deklarować wystąpienia delegatów i wywoływać delegatów. Ale jak właściwie połączyć metodę z delegatem? W tym miejscu następuje przypisanie delegata.
Aby użyć delegata, musisz przypisać do niego metodę. Metoda, którą przypisujesz, musi mieć taki sam podpis (takie same parametry i typ zwracany) jak ten, który definiuje typ delegata.
Zobaczmy praktyczny przykład. Załóżmy, że chcesz posortować listę ciągów według ich długości. Musisz utworzyć metodę porównania zgodną z podpisem delegowanym Comparison<string> :
private static int CompareLength(string left, string right) =>
left.Length.CompareTo(right.Length);
Ta metoda przyjmuje dwa ciągi i zwraca liczbę całkowitą wskazującą, który ciąg jest "większy" (dłużej w tym przypadku). Metoda jest zadeklarowana jako prywatna, co jest całkowicie w porządku. Nie musisz, aby metoda była częścią interfejsu publicznego, żeby używać jej z delegatem.
Teraz możesz przekazać metodę do metody List.Sort().
phrases.Sort(CompareLength);
Zwróć uwagę, że używasz nazwy metody bez nawiasów. Spowoduje to, że kompilator przekonwertuje odwołanie metody na delegata, który można wywołać później. Metoda Sort() wywoła metodę CompareLength za każdym razem, gdy musi porównać dwa ciągi znaków.
Można również bardziej jawnie zadeklarować zmienną delegata i przypisać do niej metodę:
Comparison<string> comparer = CompareLength;
phrases.Sort(comparer);
Oba podejścia osiągają to samo. Pierwsze podejście jest bardziej zwięzłe, podczas gdy drugie sprawia, że przypisanie delegata jest bardziej wyraźne.
W przypadku prostych metod często używa się wyrażeń lambda zamiast definiowania oddzielnej metody:
Comparison<string> comparer = (left, right) => left.Length.CompareTo(right.Length);
phrases.Sort(comparer);
Wyrażenia lambda zapewniają kompaktowy sposób definiowania prostych metod wbudowanych. Używanie wyrażeń lambda dla obiektów docelowych delegatów zostało szczegółowo omówione w dalszej sekcji.
Jak dotąd w przykładach pokazano delegatów z pojedynczą metodą docelową. Jednak obiekty delegowane mogą obsługiwać listy wywołań, które mają wiele metod docelowych dołączonych do pojedynczego obiektu delegata. Ta funkcja jest szczególnie przydatna w scenariuszach obsługi zdarzeń.
Klasy Delegat i MulticastDelegate
Za kulisami, funkcje delegata, które używasz, opierają się na dwóch kluczowych klasach w ramach .NET: Delegate i MulticastDelegate. Zwykle nie pracujesz bezpośrednio z tymi klasami, ale stanowią one podstawę, która sprawia, że delegaty działają.
Klasa i jej bezpośrednia podklasa System.DelegateSystem.MulticastDelegate zapewniają obsługę platformy do tworzenia delegatów, rejestrowania metod jako obiektów docelowych delegatów i wywoływania wszystkich metod zarejestrowanych przy użyciu delegata.
Oto interesujący szczegół projektu: System.Delegate i System.MulticastDelegate nie są typami pełnomocnictw, których można użyć. Zamiast tego służą jako klasy bazowe dla wszystkich tworzonych typów delegatów. Język C# uniemożliwia bezpośrednie dziedziczenie z tych klas — należy zamiast tego użyć słowa kluczowego delegate .
Gdy używasz słowa kluczowego delegate do deklarowania typu delegata, kompilator języka C# automatycznie tworzy klasę pochodzącą z MulticastDelegate z Twoją określoną sygnaturą.
Dlaczego ten projekt?
Ten projekt ma swoje korzenie w pierwszej wersji języków C# i .NET. Zespół projektowy miał kilka celów:
Bezpieczeństwo typu: zespół chciał upewnić się, że język wymuszał bezpieczeństwo typu podczas korzystania z delegatów. Oznacza to, że delegaty są wywoływane z poprawnym typem i liczbą argumentów oraz że typy zwracane są prawidłowo weryfikowane w czasie kompilacji.
Wydajność: Po wygenerowaniu przez kompilator konkretnych klas delegatów reprezentujących określone sygnatury metody środowisko uruchomieniowe może zoptymalizować wywołania delegatów.
Prostota: Delegaty były uwzględnione w wersji .NET 1.0, zanim wprowadzono generyki. Projekt musi działać wewnątrz czasowych ograniczeń.
Rozwiązaniem było utworzenie przez kompilator konkretnych klas delegatów, które pasują do podpisów twoich metod, co zapewnia bezpieczeństwo typów, podczas gdy złożoność pozostaje przed tobą ukryta.
Praca z metodami delegowanymi
Mimo że nie można bezpośrednio tworzyć klas pochodnych, od czasu do czasu użyjesz metod zdefiniowanych w klasach Delegate i MulticastDelegate . Oto najważniejsze z nich, o których należy wiedzieć:
Każdy delegat, z którym pracujesz, pochodzi z .MulticastDelegate Delegat "multicast" oznacza, że podczas wywołania przez delegata można uruchomić więcej niż jedną metodę. Oryginalny projekt rozważał rozróżnienie między delegatami, które mogą wywoływać tylko jedną metodę a delegatami, które mogą wywoływać wiele metod. W praktyce to rozróżnienie okazało się mniej przydatne niż pierwotnie sądzono, więc wszyscy delegaci na platformie .NET obsługują wiele metod docelowych.
Najczęściej używane metody podczas pracy z delegatami to:
-
Invoke(): Wywołuje wszystkie metody dołączone do delegata -
BeginInvoke()/EndInvoke(): używany w przypadku wzorców wywołań asynchronicznych (choćasync/awaitjest teraz preferowany)
W większości przypadków nie będziesz bezpośrednio wywoływać tych metod. Zamiast tego użyjesz składni wywołania metody w zmiennej delegata, jak pokazano w powyższych przykładach. Jednak jak zobaczysz w dalszej części tej serii, istnieją wzorce, które współpracują bezpośrednio z tymi metodami.
Podsumowanie
Teraz, gdy zobaczyłeś, jak składnia języka C# odpowiada bazowym klasom platformy .NET, możesz dowiedzieć się, jak silnie typizowane delegaty są używane, tworzone i wywoływane w bardziej złożonych scenariuszach.