Jason. Cała informatyka w jednym miejscu!

Opisałem już niezły kawał wiedzy o języku C#, natomiast to nie oznacza że już nie mam Wam nic do napisania :D. C# poza klasami, obsługuje także własne ekskluzywne możliwości dotyczące wskaźników. Nie zaszkodzi nikomu poznanie kilku szczegółów na ten temat, w jaki sposób program może sam oddziaływać na wykonywane polecenia. A uczynimy tak dzięki poznaniu słowa kluczowego "delegate" w języku C# oraz zrozumieniu działania referencji do metody. Zapraszam!

"DELEGATE" W JĘZYKU C# JESZCZE JEDNYM NAWIĄZANIEM DO JĘZYKA C

Jak widzimy słowo "referencja", to zawsze w grę wchodzi co? Wskaźnik! I nie, nie będzie tu pisania o samych stricte wskaźnikach, bo odpowiada za to inna rzecz (o "unsafe" będzie w innym artykule), jednak mają one dużo wspólnego z mechanizmem jaki zostanie na tutejszych przykładach zaprezentowany. Sam termin "delegat" jest pisany różnorako z powodu niejednoznacznej odmiany w języku polskim, dlatego też będę stosował termin "delegat".

ALE CZYM KONKRETNIE JEST TEN "DELEGAT"?

Delegat utożsamiany ze słowem kluczowym "delegate" w języku C# to jest referencja do metody albo wielu metod naraz. Mechanizm ten znany jest bardzo dobrze językowi C, w którym przy odpowiednim zapisie z użyciem wskaźnika, można tak wpłynąć na działanie aplikacji, żeby "podpięta" metoda wywołała się nie natychmiastowo, tylko po jakimś czasie lub pod wpływem jakiegoś zdarzenia (funkcja zwrotna "callback" jest doskonałym przykładem).

Delegaty często są wykorzystywane w parze ze zdarzeniami (słowo kluczowe "event"), aby zaprogramować obsługę zdarzeń. Jest to taki mechanizm, który polega na wysłaniu przez dany obiekt informacji o swoim stanie w postaci zdarzenia, a w odpowiedzi na to ma się wykonać cały kod "podpięty" pod to zdarzenie. Obiekty te nie muszą być w żaden sposób ze sobą powiązane (przekazywanie referencji w postaci metody albo referencja w danej składowej), wystarczy udostępnić zdarzenie i do niego podstawiać kolejne wywołania metod, które zostaną wyegzekwowane później.

PRZYKŁAD KODU ŹRÓDŁOWEGO

Sama teoria to za mało. Czas na ujrzenie delegata w akcji :)! Przed Waszymi nosami znajduje się najprostszy w świecie kod ukazujący występowania słowa "delegate" w języku C#:

using System;

class Program
{
	delegate void MyDelegate();
	
	static void Main(string[] args)
	{
		MyDelegate md = DoSomething;
		
		md();
	}

	static void DoSomething()
	{
		Console.WriteLine("Wypisuję komunikat!");
	}
}

Idźmy od góry. Jako daną składową umieściłem deklarację delegata:

delegate void MyDelegate();

Ważne jest żebyście wiedzieli, jak prawidłowo ją napisać.

Na początku można wstawić modyfikator dostępu. Jako że w powyższym zapisie nie ma żadnego, przyjmie ono "private". Po słowie kluczowym "delegate", wstawiamy tak jakby "szablon" nagłówka dla metod, które będą mogły zostać podstawione do naszego delegata. W tym przypadku nasz delegat będzie akceptował metody wyłącznie nieprzyjmujące żadnych parametrów formalnych (puste nawiasy) oraz niezwracające żadnej wartości (typ "void"). Może to być dowolna kombinacja np. typ zwracanej wartości "string" i jeden parametr formalny. Jak wspomniałem, to jest tylko wzór nagłówka metody celem zdefiniowania które z nich się "kwalifikują" (zawierają identyczną sygnaturę). Nazwa delegata stanowi nazwę jego typu, który będzie potem wykorzystywany jako typ instancji.

Wewnątrz statycznej metody "Main" tworzymy sobie nową instancję delegata typu "MyDelegate":

MyDelegate md = DoSomething;

Zaraz po niej następuje operator przypisania i podstawiamy sobie...metodę!!! Konkretniej to nazwę metody. Tak naprawdę, to potajemnie jest wywoływany konstruktor, do którego umieszcza się nazwę metody (tak podobno trzeba było pisać przed wersją C# 2.0):

MyDelegate md = new MyDelegate(DoSomething);

A gdzie nawiasy? Nie ma nawiasów :O! Dlaczego? Bo to jest wskaźnik do metody, a nie jej wywoływanie! Wywołanie znajduje się linijkę niżej. Żeby wywołać delegata, wystarczy do niego podejść tak samo jak do każdej innej metody. Nazwa, nawiasy okrągłe, opcjonalne parametry, średnik:

md();

Dalej już jest przykładowa metoda na potrzeby zobrazowania sytuacji, jej już nie trzeba tłumaczyć. Efekt? Wywołanie de facto metody "DoSomething". To jeszcze nie ukazuje prawdziwych możliwości słowa kluczowego "delegate" w języku C#.

PRAWDZIWY POTENCJAŁ DELEGATA

Przedstawię Wam teraz kilka sytuacji, w których delegat może okazać się niezastąpiony.

WIELOKROTNE WYWOŁYWANIE METODY

Powiedzmy, że mamy zamiar wywołać tę samą metodę kilka razy. Pętla "for"? Może być. Jednak delegat też się nada. Popatrzcie na to (całą strukturę programu już wyciąłem, żeby nie tuszować istoty sprawy):

MyDelegate md = DoSomething;

md += DoSomething;

md();

Operator dodawania do delegata :O? To żadna pomyłka. Możecie bez problemów dodać sobie referencję do tej samej metody nieskończoną liczbę razy. To spowoduje dwukrotne wywołanie metody "DoSomething"!!!

"ODEJMOWANIE" REFERENCJI

Czy w drugą stronę też...TAK! Jesteście w stanie w dowolnym momencie kodu również "wyciąć" referencję z delegata:

md -= DoSomething;

md();

co sprawi, że "DoSomething" wykona się tylko jeden raz. Uwaga! Jeden taki "minus" odejmuje za każdym razem tylko jedną sztukę. Jeżeli teraz pozbycie się tej samej metody jeszcze raz, to instancja delegata zostania skasowana (przyjmie wartość "null")! W wyniku końcowym, kolejne wywołanie delegata wyrzuci wyjątek "NullReferenceException" (język uzna, że jest już niepotrzebny)!!! Bądźcie świadomi, że to też jest obiekt, jak każdy inny! Dlatego nie zdziwcie się jak podczas programowania wywoływania zdarzenia będziecie musieli otoczyć je instrukcją warunkową sprawdzającą czy obiekt delegata nie jest równy "null".

WSTAWIANIE OSOBNEJ METODY DO TEGO SAMEGO DELEGATA

Jak w nagłówku. Macie pełnię praw do swobodnego dodawania referencji do każdej metody, o ile spełnia ona dwa kryteria dotyczące nagłówka "delegate" w języku C# (zgodna liczba parametrów i ten sam zwracany typ):

md += DoSomethingB;

przy założeniu, że macie dostęp do funkcji o takiej sygnaturze:

void DoSomethingB()
{
	// instrukcje
}

To już stanowi niepodważalny argument, który na bank zagnie sposób polegający na użyciu pętli "for" (nieregularna kolejność i liczba wywołanych metod vs. określona z góry kolejność metod wywoływanych w ustalonej kolejności).

REFERENCYJNE WIĘZI

Kolejne ciekawe zjawisko zachodzi kiedy do delegata dodamy...drugiego delegata posiadającego już jakieś podpięte referencje:

MyDelegate mdA = DoSomething;
MyDelegate mdB;

mdA += DoSomethingB;
mdA += DoSomething;
mdB = mdA;

mdA();
mdB();

Jaki skutek? Po przypisaniu referencji delegata drugiemu delegatowi:

mdB = mdA;

WSZYSTKIE referencje metod znajdujące się w delegacie przypisywanym ("mdA") zostaną osadzone wewnątrz delegata przyjmującego ("mdB"). Spowoduje to ujmując kolokwialnie "papugowanie" po delegacie A i wywołanie uruchomi identyczną sekwencję metod do egzekucji. Naturalnie nic nie stoi na przeszkodzie, żeby modyfikować tę sekwencję dodając coraz to nowe referencje, ale już do tego delegata B:

mdB += DoSomething;

Gdyby dodało się coś nowego do delegata A, ale już po przypisaniu delegatowi B, to będziecie musieli na nowo przypisać referencję celem aktualizacji!

DELEGAT JAKO "CALLBACK"

I ostatnia cecha delegata, która ukazuje daleko idące możliwości. Korzystając z "delegate" w języku C# jest całkowicie realne utworzenie sobie wywołania zwrotnego określanego jako "callback". W tym celu potrzebna jest nam dodatkowa osobna klasa. Proszę o uwagę:

KLASA "MYCLASS"
internal delegate void MyClassCallbackDelegate(MyClass mc);
	
class MyClass
{
	public void Callback(MyClassCallbackDelegate mccd)
	{
		mccd(this);
	}
}
KLASA "PROGRAM"
class Program
{
	static void Main(string[] args)
	{
		MyClass mc = new MyClass();
		MyClassCallbackDelegate mccd = MyClassCallback;
		
		mc.Callback(mccd);
	}
	
	static void MyClassCallback(MyClass mc)
	{
		Console.WriteLine("Użyto wywołania zwrotnego na MyClass!");
	}
}

"Callback" to w gruncie rzeczy wykorzystywanie kodu zewnętrznego (poza zasięgiem klasy). Banalny przykład ukazuje jak za pomocą prostej sztuczki z delegatem umieszczonym na zewnątrz obu klas, "podpiąć" kod nienależący do klasy docelowej, a który może oddziaływać na jego instancje. Po utworzeniu instancji klasy i powiązanego z nią delegata, "podpinamy" sobie od razu metodę, która stanowi kod wykonawczy dla instancji "MyClass". Żeby zachować łączność z obiektem podstawia się parametr typu "MyClass". Na końcu jest uruchomienie wywołania zwrotnego umieszczonego w klasie "MyClass" (metoda nazwana jako "Callback"), potem "zawrócenie" do miejsca startowego jakim jest funkcja "Main" (stąd taka nazwa) i wywołanie metody "MyClassCallback". Stąd po uruchomieniu programu, ujrzymy wstawiony komunikat.

Typ delegata ma modyfikator "internal" z racji tego, że domyślnie klasie także nakłada się modyfikator wewnętrzny. Gdyby delegat miałby być publiczny, to klasa "MyClass" też musiałaby być publiczna. Modyfikatory dostępu muszą być spójne, bo obie konstrukcje są zależne od siebie.

Słowo kluczowe "delegate" w języku C#

Delegat jest obiektem mogącym przechowywać nieskończenie wiele referencji do metod. Dozwolone jest dodawanie tylko tych metod, których typ i parametry formalne zgadzają się z deklaracją delegata.


Po tak wnikliwym wykładzie na temat możliwości słowa kluczowego "delegate" w języku C#, mogę już go uznać za wyjaśniony i zamknięty. Postarajcie się to dobrze opanować i bądźcie przygotowani na dalszy ciąg tego koncertu, jakim będzie wyjaśnienie jak za pomocą zdarzeń, móc umieszczać kod "odpowiadający" na sygnał wysłany przez obiekt, właśnie przy pomocy delegatów.

PODOBNE ARTYKUŁY