Delegaty należą do zaawansowych zagadnień dotyczących języka C#, zatem warto się nimi zainteresować, szczególnie dlatego, że są fundamentami do zrozumienia wątków oraz zdarzeń. Ten artykuł będzie stanowił swojego rodzaju wprowadzenie do dalszych wpisów na temat delegatów.
Czym jest delegat? Delegat to obiekt, przechowujący referencję do metody, bądź metod. Jeżeli wcześniej miałeś styczność z językiem C, możesz ich zastosowanie porównać do wskaźników. Jednak dają one dużo większe możliwości niż wskaźniki. Najprościej rzecz ujmując delegat to obiekt, który “wie” jak wywołać metodę. Jednak trochę później dowiesz się, że to spore uproszczenie.
Typ delegacyjny definiuje rodzaj metody, jaki mogą wywoływać egzemplarze delegatu.
Deklaracja delegatu wygląda następująco:
Nasz przykładowy delegat jest zgodny z każdą metodą, o typie zwrotnym int przyjmującą dwa parametry typu int. Przykładowa pasująca metoda:
Wspomniałem wcześniej o egzemplarzu delegatu. Aby utworzyć egzemplarz wystarczy przypisać pasującą metodę do zmiennej typu delegacyjnego.
Dzięki temu, egzemplarz ten możemy wywołać jak zwykłą metodę.
Delegaty muliemisji
Wszystkie egzemplarze delegatów mają zdolność muliemisji. Tak jak wcześniej wspomniałem, typ delegacyjny może zawierać referencję do wielu metod. Właśnie takie delegaty nazywamy delegatami muliemisji. Do łączenia egzemplarzy delegatów służą operatory + i +=. Na przykład:
Warto w tym miejscu zapamiętać, że w przypadku wywołania p, metody wywołają się w kolejności dodania. Analogicznie dla dodawania, działają operatory usuwania. Zastosowanie operatorów – i -= spowoduje właśnie usunięcie danej metody.
Ciekawostka: Zastosowanie operatora -= do zmiennej z jedną metodą docelową jest równoważne z przypisaniem do tej zmiennej null.
Zastosowanie i przykład
Delegaty wykorzystuje się przede wszystkim do implementacji zdarzeń oraz wywołań zwrotnych metod( zwanych call-back). Najłatwiej zrozumieć działanie na przykładach, zatem przejdźmy do rzeczy.
Zakładamy, że chcemy aby wszystkie najważniejsze elementy programu zostały załadowane na starcie. W naszym programie mamy dwie metody statyczne, które odpowiadają za wczytanie ustawień, oraz załadowanie grafik interfejsu. W tym przypadku będziemy mogli wykorzystać delegat multiemisji. Całość prezentuje się następująco:
Program.cs
Utils.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
using System; using System.Threading; namespace delegaty_podstawy_wiedza { public delegate void ProgressReporter(); public class Utils { public static void Run(ProgressReporter p) { Console.WriteLine("Liczba metod do wykonania: " + p.GetInvocationList().Length); //GetInvocationList = zwraca tablicę delegatu multiemisji foreach (Delegate d in p.GetInvocationList()) { //wywołuje pojedynczą metodę d.DynamicInvoke(); //symulacja opóźnienia Thread.Sleep(1200); } } } } |
Dzięki takiemu wykorzystaniu delegatów, mamy np. dodatkową kontrolę nad wywoływanymi metodami. Dla przykładu, uznajmy, że nie chcemy załadować domyśnych ustawień programu, ale chcemy mieć ciągle referencje do metody. W tym celu wystarczy nam zmodyfikować metodę Run()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
using System; using System.Threading; namespace delegaty_podstawy_wiedza { public delegate void ProgressReporter(); public class Utils { public static void Run(ProgressReporter p) { Console.WriteLine("Liczba metod do wykonania: " + p.GetInvocationList().Length); //GetInvocationList = zwraca tablicę delegatu multiemisji foreach (Delegate d in p.GetInvocationList()) { if (d.Method.Name != "LoadSettings") { //wywołuje pojedynczą metodę, poza LoadSettings d.DynamicInvoke(); } //symulacja opóźnienia wynikającego z pracy Thread.Sleep(1200); } } } } |