Delegaty

msm

Koncepcja delegacji w .NET

Czym właściwie są delegacje w języku C#? Najprościej można je zdefiniować jako wskaźniki do metod // opakowane w klasę (Jako że C# jest językiem wysokopoziomowym i opiera się na idei kodu zarządzanego, używanie wskaźników jest niewskazane - a wskaźników do metod jako takich nie ma). Obiekt delegata służy do przechowywania metod które obsługuje, czyli takich które mają sygnaturę zgodną z sygnaturą delegata. Rozwiązanie to jest ponadto bezpieczne, bo poprawność adresu metody jest sprawdzana za każdym razem (nie istnieje niebezpieczeństwo przetwarzania złego obszaru pamięci).

Deklarowanie typu delegata

Deklaracja delegata wygląda jak sygnatura metod przez niego obsługiwanych. Najpierw określamy modyfikator dostępu, następnie podajemy słowo kluczowe 'delegate', następnie określamy typ wartości zwracanej, następnie nazywamy delegata swoja nazwa i na końcu w nawiasie okrągłym podajemy typy wartości parametrów przekazywanych do delegata.
Typem delegata jest to co w metodach jest nazwą metody, przykład:

    // Przykład deklaracji delegata - pobiera on jeden parametr
    // typu string i nic nie zwraca.
    // Typem tego delegata jest Foo (to delegat typu Foo)
    private delegate void Foo(string param);

    // Ta metoda pasuje do sygnatury delegata.
    private void Metoda(string param) { }

    // Ta też - nazwa parametrów nie ma znaczenia.
    public void Metoda2(string s) { }

    // Delegat obsługuje tez metody statyczne, pod warunkiem ze sygnatura tej metody jest zgodna z sygnaturą delegata
    public static void MetodaStatyczna(string text) { }

    // Ta już nie - nie zgadza się typ parametru pobieranego.
    private void Metoda3(int param) { }

    // Ta też nie - zwraca string zamiast void.
    private string Metoda4(string s) { }

Delegat można zadeklarować w ciele klasy jak również poza nią. Miejsce deklaracji wpływa jedynie na zasięg dostępności delegata oraz na określenie typu delegata:

    public delegate string Delegat_A(string text1, string text2);

    public class JakasKlasa
    {
        public delegate void Delegat_B(string text1, string text2);
        //ciało klasy
    }

W powyższym przykładzie typem delegata zadeklarowanego poza klasą jest Delegat_A a typem delegata zadeklarowanym w ciele klasy jest JakasKlasa.Delegat_B.

Zanim przejdziemy do przypisywania metod do delegata, warto zapoznać się z wymyślonym na potrzeby tego artykułu przypadkiem problemu i sposobem rozwiązania go za pomocą delegatów. W miarę postępu artykułu dochodzić będziemy do kolejnych aspektów pracy z delegatowi w tym do tego na czym polega istota delegatu czyli do przypisywania do niego metod.

##Typy delegatów do ogólnego użytku

Platforma .NET udostępnia zestaw delegatów generycznych do ogólnego użytku. Dzięki nim nie jest konieczne tworzenie własnych delegatów, można użyć gotowych. Definiowanie własnych delegatów jest jednak zasadne w momencie gdy ma to służyć zwiększeniu czytelności kodu.
Do rodziny delegatów System.Func można delegować metody które zwracają jakąś wartość a do rodziny delegatów System.Action metody które nie zwracają żadnej wartości (void).
Jedna i druga rodzina udostępnia 17 sygnatur każdego z delegatów. Sygnatury dopasowane są do metod od nieprzyjmujących żadnego parametru, do metod przyjmujących 16 argumentów.

Rodzina delegatów System.Action:

    public delegate void Action();
    public delegate void Action<in T>(T arg);
    public delegate void Action<in T1, in T2>(T1 arg1, T2 arg2);
    public delegate void Action<in T1, in T2, in T3>(T1 arg1, T2 arg2, T3 arg3);
    //(...)
    public delegate void Action<
        in T1, in T2, in T3, in T4, in T5, in T6, in T7, in T8,
        in T9, in T10, in T11, in T12, in T13, in T14, in T15, in T16>(
        T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8,
        T9 arg9, T10 arg10, T11 arg11, T12 arg12, T13 arg13, T14 arg14, T15 arg15, T16 arg16);

Rodzina delegatów System.Func:

    public delegate TResult Func<out TResult>();
    public delegate TResult Func<in T, out TResult>(T arg);
    public delegate TResult Func<in T1, in T2, out TResult>(T1 arg1, T2 arg2);
    public delegate TResult Func<in T1, in T2, in T3, out TResult>(T1 arg1, T2 arg2, T3 arg3);
    //(...)
    public delegate TResult Func<
        in T1, in T2, in T3, in T4, in T5, in T6, in T7, in T8,
        in T9, in T10, in T11, in T12, in T13, in T14, in T15, in T16, out TResult>(
        T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8,
        T9 arg9, T10 arg10, T11 arg11, T12 arg12, T13 arg13, T14 arg14, T15 arg15, T16 arg16);

##Przykład użycia delegata ogólnego

**Opis problemu. **

Piszemy program który ma przetwarzać jakieś dokumenty urzędowe, różnego rodzaju. To co jest zmienne to to, że w zależności od typu dokumentu należy inaczej go podpisać, np. jeden trzeba podpisać tak, gdzie imię jest przed nazwiskiem, drugi gdzie nazwisko jest przed imieniem jeszcze inny samymi inicjałami.
Mamy klasę 'WniosekUrzedowy' który reprezentuje jakiś dokument i działa na nim, jedną z jego metod jest 'PodpisanieDokumentu'.
I teraz aby rozwiązać problem wymogu różnego podpisywania w zależności od rodzajudokuemntu możemy oprócz imienia i nazwiska przekazywanych w parametrach metody dodać jakiś 'enum' który będzie określał typ dokumentu a w ciele metody 'PodpisanieDokumentu' wstawimy instrukcje warunkową 'switch' która w zależności od wartości reprezentującej typ dokumentu będzie odpowiednio podpisywała wniosek.

   enum TypWniosku
    {
        RodzajWnioskuA,
        RodzajWnioskuB,
        RodzajwnioskuC
    }
    class OgolnyWniosekUrzedowy
    {
        //(...) 
        public string wartoscPodpisu;

        public void PodpisywanieWniosku(string imie, string nazwisko, TypWniosku typWniosku)
        {

            bool bledyFormalne = false;
            //weryfikacje poprawnosci wniosku 

            if (bledyFormalne == true)
            {
                switch (typWniosku)
                {
                    case TypWniosku.RodzajWnioskuA:
                        wartoscPodpisu = PodpiszJakoImieNazwisko(imie, nazwisko);
                        break;
                    case TypWniosku.RodzajWnioskuB:
                        wartoscPodpisu = PodpiszJakoNazwiskoImie(imie, nazwisko);
                        break;
                    case TypWniosku.RodzajwnioskuC:
                        wartoscPodpisu = PodpiszJakoInicjaly(imie, nazwisko);
                        break;
                    default:
                        break;
                }
            }
        }

        private string PodpiszJakoImieNazwisko(string imie, string nazwisko)
        {
            return imie + " " + nazwisko;
        }

        private string PodpiszJakoNazwiskoImie(string imie, string nazwisko)
        {
            return nazwisko + " " + imie;
        }

        private string PodpiszJakoInicjaly(string imie, string nazwisko)
        {
            return $"{imie[0]}{nazwisko[0]}";
        }
    }

No i działa i jest OK.

            OgolnyWniosekUrzedowy wniosek = new OgolnyWniosekUrzedowy();
            //(...)
            //przetwarzanie wniosku

            wniosek.PodpisywanieWniosku("Jan", "Kowalski", TypWniosku.RodzajWnioskuB);
            //aby zadzialalo, wewnatrz metody PodpisywanieWniosku 
            //w if'ie nalezy spelnić warunek, czyli wartosc bledyFormalne
            //nalezy ustawic na true
            Console.WriteLine(wniosek.wartoscPodpisu);

Problem pojawia się gdy dochodzi nowy typ dokumentu który należy fantazyjnie podpisać swoim imieniem i nazwiskiem ale pisanymi od tylu.
Nic wielkiego, można dodać jeszcze jeden typ dokumentu do enuma, dodać jeszcze jedną metodę do klasy ‘OgolnyWniosekUrzedowy’ a do metody ‘PodpisywanieWniosku’ do instrukcji sterującej ‘switch’ dodać jeszcze jeden ‘case’. Nowa metoda wyglądałaby tak.

    private string PodpiszJakoImieNazwisko_Wspak(string imie, string nazwisko)
    {
        string nazwa = imie + " " + nazwisko;
        char[] tablica = nazwa.ToCharArray();
        Array.Reverse(tablica);

        return new String(tablica);
    }

Bezsprzecznie będzie działało. Ale takimi działaniami łamiemy jedną z zasad SOLID, mianowicie wprowadzając zmiany w programie musimy „grzebnąć” w klasie a przecież wg SOLID klasa ma być zamknięta na modyfikacje ale otwarta na rozszerzanie. Spełnienie tego wymogu można zrealizować na parę sposób jednym z nich jest wykorzystanie delegata.
Tak się składa ;), że wszystkie metody odpowiedzialne za podpisywanie wniosku mają ten sam typ zwracany – 'string', i po dwa parametry pobierane również oba typu 'string'.

    private string PodpiszJakoImieNazwisko(string imie, string nazwisko)
    private string PodpiszJakoNazwiskoImie(string imie, string nazwisko)
    private string PodpiszJakoInicjaly(string imie, string nazwisko)
    private string PodpiszJakoImieNazwisko_Wspak(string imie, string nazwisko)

dlatego pozostaje nam tylko określić typ delegata jaki będzie obsługiwał tego typu metody, typem tym będzie (ponieważ metody zwracają wartość) delegat 'Func<…>'.
Wykorzystamy to, że delegat jest obiektem a skoro tak może być przekazywany jako obiekt, poprzez parametr do metody. Teraz metodę odpowiedzialną za podpisywanie wniosku można napisać tak:

    public void PodpisywanieWniosku(string imie, string nazwisko, Func<string, string, string> stempelek)
    {
        bool bledyFormalne = false;
        //weryfikacje poprawnosci wniosku 

        if (bledyFormalne == true)
        {
            // w tym miejscu brakuje ważnego elementu kodu, podany zostanie na końcu artykulu
            wartoscPodpisu = stempelek(imie, nazwisko);
        }
    }

Delegat ‘Func<string, string, string>’ określa że pierwsze dwa stringi w jego nawiasie trójkątnym to typ parametrów metody która do niego pasuje a ostatni string z jego nawiasu trójkątnego to typ jaki ma zwracać metoda która do niego pasuje. Czyli wszystkie metody powyżej z przykładu, generujące podpis, pasują do niego. Ponadto skoro delegat to obiekt, nazwaliśmy go ‘stempelek’ i jako ‘stempelek’ występuje w ciele metody ‘PodpisywanieWniosku’ w instrukcji warunkowej ‘if’, wywoływany jest poprzez swoją nazwę jak zwykła metoda wewnętrzna klasy ‘OgolnyWniosekUrzedowy’. Podczas wywoływania go przekazywane ma dwa parametry wejściowe ‘imie’ i ‘nazwisko’ typu ‘string’, jak widać zwracać tez będzie wartość typu ‘string’.
Jednak przez takie wywołanie delegata, tak naprawdę wywołuje się to co delegat ma w sobie, czyli jakie metody zostały do niego dodane.
Zanim przejdziemy do sprawdzenia działania tego przykładu, wyjaśnijmy co się stało z metodami generującymi podpis. Metody te, skoro odpowiadają za to samo – za generowanie podpisu, zostały przeniesione do oddzielnej klasy której jedyną odpowiedzialnością jest wygenerowanie podpisu. W klasie tej na potrzeby tego przykładu dwie metody zostały zdefiniowane jako statyczne.

    public class GeneratorPodpisow
    {
        public string PodpiszJakoImieNazwisko(string imie, string nazwisko)
        {
            return imie + " " + nazwisko;
        }

        public static string PodpiszJakoNazwiskoImie(string imie, string nazwisko)
        {
            return nazwisko + " " + imie;
        }

        public string PodpiszJakoInicjaly(string imie, string nazwisko)
        {
            return $"{imie[0]}{nazwisko[0]}";
        }

        public static string PodpiszJakoImieNazwisko_Wspak(string imie, string nazwisko)
        {
            string nazwa = imie + " " + nazwisko;
            char[] tablica = nazwa.ToCharArray();
            Array.Reverse(tablica);

            return new String(tablica);
        }
    }

Wszystkie klocki zostały przygotowane teraz ułóżmy scenariusz w konsoli jak tego użyć i jak sprawdzić czy działa.

            OgolnyWniosekUrzedowy wniosek = new OgolnyWniosekUrzedowy();
            //(...)

            //Przypadek gdy przekazujemy metode statyczną            
            wniosek.PodpisywanieWniosku("Jan", "Kowalski", GeneratorPodpisow.PodpiszJakoNazwiskoImie);  //uwaga na wartość bledyFormalne w warunku if w tej metodzie ;)
            Console.WriteLine(wniosek.wartoscPodpisu);
            Console.ReadLine();

            //Przypadek gdy przekazujemy metode niestatyczną
            GeneratorPodpisow podpisywacz = new GeneratorPodpisow();
            wniosek.PodpisywanieWniosku("Jasiek", "Kowalewski", podpisywacz.PodpiszJakoImieNazwisko);   //uwaga na wartość bledyFormalne w warunku if w tej metodzie;)
            Console.WriteLine(wniosek.wartoscPodpisu);
            Console.ReadLine();

Dwie ważne rzeczy do zaobserwowania, pierwsza to sposób przekazywania metody statycznej i niestatycznej jako parametr do metody ‘PodpisywanieWniosku’, w której to metodzie w definicji występuje określony typ delegata ‘Func<in string, in string, out string>’.
Druga ważna rzecz to to, że w powyższym przypadku delegat służy jako definicja typu danych które mają zostać przekazane poprzez parametr do metody. Nie ma tu nigdzie jawnego przypisywania metody do delegata poprzez operator ‘+=’. Za każdym wywołaniem metody ‘PodpisywanieWniosku’ możemy „wstrzyknąć” do obiektu ‘OgolnywniosekUrzedowy’ różną metodę podpisywania wniosku, ta metoda zostanie w tle „opakowana” w delegata i już z poziomu delegata wywoływana w ciele metody.
Takie samo „opakowywanie” w delegata dzieje się gdy zamiast wskazania na metodę, zapiszemy od razu wyrażenie lambda. Takie wyrażenie również zostaje opakowywane w tle w delegata i w ciele metody wywoływane już przez delegata. Tak jak na przykładzie niżej:

            WniosekUrzedowy.PodpisywanieWniosku("imie", "nazwisko", (imie, nazwisko) => ( imie + " " + nazwisko) );

Definiowane własnego delegata i przykład jego użycia

Zakładając że wszystkie przykłady z artykułu są pisane i sprawdzane w default’owej aplikacji konsolowej, klasy są pisane w tej samej przestrzeni nazw co klasa główna ‘Program’ a kod sprawdzający pisany jest w metodzie ‘Main’. Jednego delegata ‘Podpisywacz’ zdefiniujemy w przestrzeni nazw poza klasą ‘Program’, drugiego ‘Podpisywacz_2’ w klasie ‘Program’ tak jak niżej.

    public delegate string Podpisywacz(string imie, string nazwisko);

    public class Program
    {
        public delegate string Podpisywacz_2(string imie, string nazwisko);

        static void Main(string[] args)
        {
            //(…)
        }
    }

To gdzie zadeklarowaliśmy delegata wpływa jedynie na sposób w jaki będziemy określać typ delegata, oraz w jaki określamy zakres widoczności tego delegata. Nie możemy zdefiniować delegata w klasie statycznej, klasy statyczne narzucają wymóg definiowania wszystkich swoich składowych jako statyczne a delegat statyczny być nie może.
Poniżej sposób jak przerobiono metodę ‘PodpisywanieWniosku’ aby dostosować ją do naszego zdefiniowanego delegata. (W pierwszym przypadku dla delegata zdefiniowanego na tym samym poziomie co klasa ‘Program’, a w drugim przypadku dla delegata zadeklarowanego wewnątrz klasy ‘Program’).
Przypadek pierwszy – delegat zadeklarowany poza klasą ‘Program’.

    public void PodpisywanieWniosku(string imie, string nazwisko, Podpisywacz podpisywacz)
    {
        bool bledyFormalne = true;
        //weryfikacje poprawnosci wniosku 

        if (bledyFormalne == true)
        {
            wartoscPodpisu = podpisywacz(imie, nazwisko);
        }
    }

Kod klasy ‘GeneratorPodpisow’ nie zmienia się, sposób sprawdzenia wyniku, poniżej:

            OgolnyWniosekUrzedowy wniosek = new OgolnyWniosekUrzedowy();
            //(...)
            Podpisywacz stempel = GeneratorPodpisow.PodpiszJakoNazwiskoImie;


            //Przypadek gdy przekazujemy metode statyczną            
            wniosek.PodpisywanieWniosku("Jan", "Kowalski", stempel);
            Console.WriteLine(wniosek.wartoscPodpisu);
            Console.ReadLine();

            //Przypadek gdy przekazujemy metode niestatyczną
            GeneratorPodpisow podpisywacz = new GeneratorPodpisow();
            stempel = podpisywacz.PodpiszJakoImieNazwisko;

            wniosek.PodpisywanieWniosku("Jasiek", "Kowalewski", stempel);
            Console.WriteLine(wniosek.wartoscPodpisu);
            Console.ReadLine();

Przypadek drugi – delegat zadeklarowany wewnątrz klasy ‘Program’.

    public void PodpisywanieWniosku(string imie, string nazwisko, Program.Podpisywacz_2 podpisywacz)
    {
        bool bledyFormalne = false;
        //weryfikacje poprawnosci wniosku 

        if (bledyFormalne == true)
        {
            wartoscPodpisu = podpisywacz(imie, nazwisko);
        }
    }

Kod klasy ‘GeneratorPodpisow’ nie zmienia się, sposób sprawdzenia wyniku, poniżej:

            OgolnyWniosekUrzedowy wniosek = new OgolnyWniosekUrzedowy();
            //(...)

            Podpisywacz_2 stempel = GeneratorPodpisow.PodpiszJakoNazwiskoImie;


            //Przypadek gdy przekazujemy metode statyczną            
            wniosek.PodpisywanieWniosku("Jan", "Kowalski", stempel);
            Console.WriteLine(wniosek.wartoscPodpisu);
            Console.ReadLine();

            //Przypadek gdy przekazujemy metode niestatyczną
            GeneratorPodpisow podpisywacz = new GeneratorPodpisow();
            stempel = podpisywacz.PodpiszJakoImieNazwisko;

            wniosek.PodpisywanieWniosku("Jasiek", "Kowalewski", stempel);
            Console.WriteLine(wniosek.wartoscPodpisu);
            Console.ReadLine();

Gdybyśmy zdefiniowali delegat ‘Podpisywacz_3’ w klasie ‘OgolnyWniosekUrzedowy’ to w klasie ‘Program’ odnosilibyśmy się do niego poprzez ‘OgolnyWniosekUrzedowy.Podpisywacz_3’.
Na co warto zwrócić uwagę, to linia gdzie w klasie ‘Program’ definiujemy typ delegata, po prostu przypisujemy do obiektu ‘stempel’ metodę ‘GeneratorPodpisow.PodpiszJakoNazwiskoImie’ . Obiekt jest tworzony a nie używane jest słowo kluczowe ‘new’.
Równoważny zapis wyglądałby tak:

    Podpisywacz stempel = new Podpisywacz(GeneratorPodpisow2.PodpiszJakoNazwiskoImie);

I to również zadziała ale w przypadku delegatów jest nadmiarowe i nie stosowane.

Przypisanie do delegata większej liczby metod

W tym przypadku użyjemy tylko operatora '+=' w obecnym konie w metodzie ‘Main’, wyglądać to będzie tak:

            OgolnyWniosekUrzedowy wniosek = new OgolnyWniosekUrzedowy();
            //(...)
            GeneratorPodpisow podpisywacz = new GeneratorPodpisow();

            Podpisywacz stempel = GeneratorPodpisow.PodpiszJakoNazwiskoImie;
            stempel += podpisywacz.PodpiszJakoImieNazwisko;
            stempel += podpisywacz.PodpiszJakoInicjaly;
            stempel += GeneratorPodpisow.PodpiszJakoImieNazwisko_Wspak;

            //Przypadek gdy wywolujemy delegata zawierajacego wiecej metod          
            wniosek.PodpisywanieWniosku("Jan", "Kowalski", stempel);
            Console.WriteLine(wniosek.wartoscPodpisu);            
            
            Console.ReadLine();

Za pomocą operatora '-=' można również usuwać wcześniej dodane metody do delegata.

Wynik z powyższego kodu jednak zwróci tylko jeden podpis – tylko z ostatnio dodanej metody. Dzieje się tak dlatego, że tylko raz wywołujemy metode 'Console.WriteLine()' a ona przyjmuje wartość zwrotną z delegata jako 'string' a nie tablica string’ów. I dlatego wypisuje tylko jedną wartość zwracaną przez ostatnio wykonywana metodę.
Aby sprawdzić czy aby na pewno wszystkie metody są wywoływane można najpierw zajrzeć do delegata co ona ma tam w środku:

            OgolnyWniosekUrzedowy wniosek = new OgolnyWniosekUrzedowy();
            //(...)
            GeneratorPodpisow podpisywacz = new GeneratorPodpisow();

            Podpisywacz stempel = GeneratorPodpisow.PodpiszJakoNazwiskoImie;
            stempel += podpisywacz.PodpiszJakoImieNazwisko;
            stempel += podpisywacz.PodpiszJakoInicjaly;
            stempel += GeneratorPodpisow.PodpiszJakoImieNazwisko_Wspak;

            //Przypadek gdy wywolujemy delegata zawierajacego wiecej metod          
            wniosek.PodpisywanieWniosku("Jan", "Kowalski", stempel);
            Console.WriteLine(wniosek.wartoscPodpisu);

            Delegate[] zawartosc = stempel.GetInvocationList();
            foreach (var item in zawartosc)
            {
                Console.WriteLine(item.Method);
            }

            Console.ReadLine();

Powyżej sprawdzamy zawartość delegata i wypisujemy przypisane do niego pełne sygnatury metod, czyli typ zwracany, nazwę metody i typu parametrów wejściowych metody.
Wynik w konsoli:

System.String PodpiszJakoNazwiskoImie(System.String, System.String)
System.String PodpiszJakoImieNazwisko(System.String, System.String)
System.String PodpiszJakoInicjaly(System.String, System.String)
System.String PodpiszJakoImieNazwisko_Wspak(System.String, System.String)

Czyli wszystko się zgadza nawet kolejność metod odpowiada kolejności dodawania tych metod do delegata. Jednak jeżeli chodzi o kolejność metod wywoływanych w delegacie to tu nie mamy żadnej kontroli nad delegatem w jakiej kolejności on to powywołuje – tak po prostu jest i nie należy budować programu na założeniu ze oddelegujemy wiele kolejnych metod tworzących łańcuch wywołań który ma sens tylko jeżeli będzie zachowana kolejność wywołań tych metod. Delegat nie musi przestrzegać tej kolejności wywołań i może to doprowadzić do błędów funkcjonowania programu.
Jednak powyżej sprawdziliśmy tylko co delegat ma w sobie ale nie sprawdziliśmy czy on to w ogóle wywołuje. Aby wywołać wypisanie wszystkich rodzajów podpisów z wszystkich metoda zawartych w delegacie należy przerobić kod w takim zakresie:

  • zmienić delegata na takiego którego wartością zwracaną będzie void

  • przerobić metody klasy tak aby metody zwracały void a wywołanie w ciele metody instrukcji return zastąpić jawnie podaną metodąConsole.WriteLine(), do metody WriteLine() jako oargument, przekazać to co do tej pory zwracał return.

Tak przerobiony kod zwróci wszystkie rodzaje podpisów na konsoli z wywołania wszystkich metod zawartych w delegacie.

Ważne uzupełnienie kodu

W ciele metody ‘PodpisywanieWniosku’ jest warunek wywołania delegata który opiera się tylko na sprawdzeniu czy pole ‘bledyFormalne’ jest równe’ true’

            if (bledyFormalne == true)
            {
                wartoscPodpisu = podpisywacz(imie, nazwisko);
            } 

W praktyce zawsze należy sprawdzać czy delegat ma w sobie cos przypisane, ponieważ gdybyśmy żadnej metody nie przypisali do delegata ‘podpisywacz’, próba wywołania tego delegata zwróciła by wyjątek ‘NullReferenceException’. Dlatego zawsze przed wywołaniem delegata należy sprawdzać czy nie jest pusty np. tak:

            if (bledyFormalne == true && podpisywacz != null)
            {
                wartoscPodpisu = podpisywacz(imie, nazwisko);
            }

Na koniec warto wspomnieć że operatory '+=' i '-=' wywołują w tle (a właściwie są tym samym co) metody 'Combine' i 'Remove' klasy delegata.

2 komentarzy

Idealnie wytłumaczone w końcu wiem o co chodzi. Profesorowi, który jak tłumaczy to tak jakby sam do końca tego wszystkiego nie rozumiał średnio to wychodzi. Moim zdaniem, jeśli potrafisz coś komuś dobrze wytłumaczyć to jest to równoznaczne, że znasz się na temacie.

Czy te funkcje Metoda, Metoda2, Metoda4 nie powinny być statyczne?