C#

Zdarzenia

Degusto

Artykuł jest podzielony na dwie części, w pierwszej zdarzenia są omawiane na praktycznym przykładzie, w drugiej części, autorstwa Degusto, jest artykuł zdecydowanie prostszy w odbiorze.

Cześć 1.

Przykładem jest program gdzie zdefiniowane są klasy ‘Punkt’, ‘Odcinek’ i ‘Trójkąt’. Założenie jest takie, że tworzymy kilka punktów, następnie używamy ich do stworzenia trzech odcinków i trójkąta. Finalnie oczekujemy, że za każdym razem gdy którykolwiek punkt zmieni którąkolwiek swoją współrzędną, odcinki automatycznie przeliczą swoje długości a trójkąt przeliczy swój obwód i pole powierzchni.
Zaznaczyć trzeba, że założenie te można, a nawet trzeba, zrealizować dzięki właściwościom odcinka i trójkąta, w getterach tych właściwości wystarczy wkleić ciała metod liczących długość, obwód czy pole. Niemniej jednak do prezentacji implementacji zdarzenia, przykład może okazać się bardzo obrazowy i intuicyjny.
Zastosowanie w tym przypadku zdarzeń będzie można odczuć dzięki czwartemu obiektowi który będzie obserwatorem wszystkich generowanych zdarzeń, przez wszystkie punkty.

Klasa ‘Punkt’:

    public class Punkt
    {
        public delegate void ZmianaWspolrzednychHandler(Punkt sender, WspolrzedneArgs args);
        public event ZmianaWspolrzednychHandler OnZmianaWspolrzednychPunktu;

        public Punkt(int X, int Y)
        {
            this.X = X;
            this.Y = Y;
            Nazwa = $"{NazywaczPunktow.PodajKolejnyIdentyfikatorPunktu()}";
        }

        public int X
        {
            get { return _wspolrzednaX; }
            set
            {
                if (_wspolrzednaX != value)
                {
                    archX = _wspolrzednaX;
                    _wspolrzednaX = value;
                    OnZmianaWspolrzednychPunktu?.Invoke(this, new WspolrzedneArgs(archX, value, Wspolrzedna.X));
                }
            }
        }
        public int Y
        {
            get { return _wspolrzednaY; }
            set
            {
                if (_wspolrzednaY != value)
                {
                    archY = _wspolrzednaY;
                    _wspolrzednaY = value;
                    OnZmianaWspolrzednychPunktu?.Invoke(this, new WspolrzedneArgs(archY, value, Wspolrzedna.Y));
                }
            }
        }
        public string Nazwa { get; private set; }

        public override string ToString()
        {
            return $"Punkt ({Nazwa}): X:{X}; Y:{Y}";
        }
        private int _wspolrzednaX;
        private int _wspolrzednaY;
        private int archX;
        private int archY;
    }

Omówienie klasy Punkt.
Na samej górze klasy w pierwszej linii znajduje się zadeklarowany typ delegata którego będziemy używać. Ta deklaracja znajduje się wewnątrz klasy ‘Punkt’, jednak zadeklarować delegata możemy również poza klasą dlatego możemy go przenieść jak widać poniżej, nie zmieni to nic w funkcjonalności programu a może pomóc w czytelności.

    public delegate void ZmianaWspolrzednychHandler(Punkt sender, WspolrzedneArgs args);

    public class Punkt
    {
        public event ZmianaWspolrzednychHandler OnZmianaWspolrzednychPunktu;

        public Punkt(int X, int Y)
        {
            this.X = X;
            this.Y = Y;
            Nazwa = $"{NazywaczPunktow.PodajKolejnyIdentyfikatorPunktu()}";
        }
        //(...)
    }

Wracając do omówienia zadeklarowanego w klasie ‘Punkt’ delegata:

  • Typem tego delegata jest ‘ZmianaWspolrzednychHandler’
  • Delegat będzie przyjmował tylko takie metody które nie zwracają wartości (świadczy o tym ‘void’ przed typem) oraz przyjmują dwa parametry wejściowe, pierwszy typu ‘Punkt’ i drugi typu ‘WspolrzedneArgs’ (ten drugi typ będzie omówiony później)

Ale to tylko deklaracja, tak jak deklaruje się klasę a dopiero na podstawie deklaracji tworzy się obiekt tej klasy, tak samo delegat, na podstawie tej deklaracji w drugiej linii w klasie ‘Punkt’ zostaje utworzony obiekt delegata typu ‘ZmianaWspolrzednychHandler’.

Ten obiekt delegata ma słowo kluczowe ‘event’ zaraz za modyfikatorem dostępu ‘public’, gdyby je usunąć, program działałby dalej. Jedyna różnica którą robi to słowo, zostanie omówione przy okazji omawiania klasy ‘Odcinek’ i ‘Trójkąt’. I będzie to istotna różnica jeżeli chodzi o bezpieczeństwo.
Utworzony w klasie ‘Punkt’ obiekt delegata jest typu ‘ZmianaWspolrzednychHandler’ i ma nazwę ‘OnZmianaWspolrzednychPunktu’.

W obiekcie delegata będą przetrzymywane wszystkie metody obsługi zdarzenia obiektów, które to obiekty będą obserwować zmiany współrzędnych punktów. Czyli będą to metody obsługi zdarzenia zmiany współrzędnych, zdefiniowane w ‘Odcinku’ i ‘Trójkącie’. Ale aby ‘Odcinek’ i ’Trójkąt’ mógł się zapisać jako obserwatorzy zdarzenia zmiany współrzędnych, czyli aby mogli przekazać swoje metody obsługi tego zdarzenia do obiektu delegata klasy ‘Punkt’, te metody obsługi zdarzenia mają mieć sygnaturę identyczną jak sygnatura delegata. Chodzi o to aby również zwracały ‘void’, również pobierały dwa parametry, pierwszy typu ‘Punkt’ i drugi typu ‘WspolrzedneArgs’.

Przyjrzyjmy się teraz fragmentowi który odpowiada za zmianę współrzędnej X punktu w klasie ‘Punkt’,

    public int X
    {
        get { return _wspolrzednaX; }
        set
        {
            if (_wspolrzednaX != value)
            {
                archX = _wspolrzednaX;
                _wspolrzednaX = value;
                OnZmianaWspolrzednychPunktu?.Invoke(this, new WspolrzedneArgs(archX, value, Wspolrzedna.X));
            }
        }
    }

W setterze, w pierwszej kolejności sprawdzane jest czy wprowadzana nowa wartość współrzędnej X, nie jest przypadkiem taka sama jak wartość obecnej współrzędnej, jeżeli są sobie równe to nic się nie zmieni a jeżeli tak, to nic się nie dzieje w obiekcie, ani nowa wartość nie jest przypisywana do pola ‘_wspolrzednaX’ ani nie jest wywoływane zdarzenie zmiany wspolrzednych.
Natomiast jeżeli przypisywana jest inna wartość niż obecna, to najpierw archiwizowana jest stara wartość współrzędnej, następnie przypisuje się nową wartość do pola ‘_wspolrzednaX’ a jako ostatnie uruchamia się metody obsługi zdarzeń wszystkich subskrybentów. I ta linia wymaga najwięcej uwagi.

OnZmianaWspolrzednychPunktu?.Invoke(this, new WspolrzedneArgs(archX, value, Wspolrzedna.X));

Operator ‘?.’ służy do sprawdzenia przed wywołaniem wszystkich metod delegata czy ten delegat nie jest pusty, jeżeli jest pusty delegat nie będzie wywoływany ponieważ wywołanie pustego delegata zwraca wyjątek ‘NullReferenceException’. Ta linia jest równoważna z takim kodem:

    if (OnZmianaWspolrzednychPunktu!=null)
    {
         OnZmianaWspolrzednychPunktu.Invoke(this, new WspolrzedneArgs(archX, value, Wspolrzedna.X));
    }

Operator ‘?.’ daje jednak małe coś extra w stosunku do sprawdzania ‘if’em’. Podczas sprawdzania czy delegat nie jest ‘null’em’ operatorem ‘?.’, mamy gwarancję, że specjalny mechanizm w tle nie pozwoli wypisać się subskrybentom pomiędzy sprawdzeniem a wywołaniem delegata. Podczas sprawdzania if’em może dojść do takiej sytuacji gdzie warunek sprawdzania w ‘if’ie’ zwróci ‘true’, czyli potwierdzi ze delegat nie jest ‘null’em’ i zaraz po tym sprawdzenia a przed wywołaniem delegata w innym wątku cos wypisze metody z delegata i pozostawi delegata pustym. Operator ‘?.’ do czegoś takiego nie dopuści, dlatego warto taką składnie stosować.
Również należy przyjąć zasadę ze wywołujemy delegata używając metody: Delegate.Invoke(delegate);.

Teraz przyjrzyjmy się jak wywołuje się metody zapisane w delegacie.

OnZmianaWspolrzednychPunktu?.Invoke(this, new WspolrzedneArgs(archX, value, Wspolrzedna.X));

Wypełnia się tu zobowiązanie wynikające z typu stosowanego delegata

public delegate void ZmianaWspolrzednychHandler(Punkt sender, WspolrzedneArgs args);

czyli w metodzie wywołującej delegata ‘Invoke’, jako parametry przekazywane do delegata jako pierwszy przekazywany jest punkt w którym dochodzi do zmiany współrzędnej X (przekazuje sam siebie jako obiekt typu ‘Punkt’). Następnie jako drugi parametr przekazywany jest nowo tworzony obiekt typu ‘WspolrzedneArgs’ wraz z podaniem mu oczekiwanych przez jego konstruktor parametrów: stara współrzędna, nowa współrzędna oraz która to współrzędna się zmienia.
Definicję tej metody można zobaczyć poniżej.

Klasa WspolrzedneArgs

    public class WspolrzedneArgs : EventArgs
    {
        public WspolrzedneArgs(int staraWartosc, int nowaWartosc, Wspolrzedna zmienianaWspolrzedna)
        {
            Stara = staraWartosc;
            Nowa = nowaWartosc;
            ZmienianaWspolrzedna = zmienianaWspolrzedna;
        }
        public int Stara { get; private set; }
        public int Nowa { get; private set; }
        public Wspolrzedna ZmienianaWspolrzedna { get; set; }
    }

    public enum Wspolrzedna
    {
        X,
        Y
    }

Klasa WspolrzedneArgs dziedziczy po EventArgs (ale w tym przypadku nie musi dziedziczyć). Obiekt tej klasy zawiera wszelkie dane dotyczące zdarzenia jakie wystąpiło. Jest to pewna konwencja która nakazuje do typu delegata dodawać zawsze słowo ‘Handler’ a jako parametry zdefiniowanego delegata warto przekazywać (object sender, EventArgs e). Dzięki temu łatwiej zorientować się co jest czym w kodzie, analogicznie jak konwencja nazywania własnych interfejsów z prefiksem ‘I’.

Do pełnego omówienia klasy ‘Punkt’ pozostała nam już tylko jedna linia w konstruktorze:

Nazwa = $"{NazywaczPunktow.PodajKolejnyIdentyfikatorPunktu()}";

Użyta tu klasa ‘NazywaczPunktow’ wygląda jak niżej. Jest to klasa statyczna której jedyną odpowiedzialnością jest podanie unikalnej sygnatury punktu za każdym wywołaniem jej metody ‘PodajKolejnyIdentyfikatorPunktu()’.

Klasa NazywaczPunktow

    public static class NazywaczPunktow
    {
        private static int licznikNazw = 0;
        private static int surfiks = 1;
        private static char[] literki = new char[]   { 'A', 'B', 'C', 'D', 'E',
                                                       'F', 'G', 'H', 'I', 'J',
                                                       'K', 'L', 'M', 'N', 'O',
                                                       'P', 'Q', 'R', 'S', 'T',
                                                       'U', 'W', 'X', 'Y', 'Z' };

        public static string PodajKolejnyIdentyfikatorPunktu()
        {
            string sygnaturaPunktu = literki[licznikNazw].ToString() +
                            ((surfiks != 1) ? surfiks.ToString() : "");

            if (licznikNazw % 24 == 0 && licznikNazw != 0)
            {
                surfiks++;
                licznikNazw = -1;
            }
            licznikNazw++;
            return sygnaturaPunktu;
        }
    }

Klasa Odcinek.

    public class Odcinek
    {
        public Punkt PunktP { get; private set; }    //PunktP -> Poczatkowy
        public Punkt PunktK { get; private set; }    //PunktK -> Koncowy
        public double Dlugosc { get; private set; }

        public Odcinek(Punkt a, Punkt b)
        {
            PunktP = a;
            PunktK = b;
            Dlugosc = ObliczDlugosc();
            a.OnZmianaWspolrzednychPunktu += this.OnZmianaWspolrzednych;
            b.OnZmianaWspolrzednychPunktu += this.OnZmianaWspolrzednych;
        }

        public double ObliczDlugosc()
        {
            return WielkiMatematyk.OdlegloscMiedzyPunktami(PunktP, PunktK);
        }

        public void OnZmianaWspolrzednych(Punkt sender, WspolrzedneArgs e)
        {
            Dlugosc = ObliczDlugosc();
        }

        public override string ToString()
        {
            return $"Odcinek \"{PunktP.Nazwa}{PunktK.Nazwa}\", o dlugosci: {Dlugosc}";
        }
    }

Omówienie klasy Odcinek.

Jeszcze raz na wstępie przypomnę, że funkcjonalność aby odcinek mógł aktualizować wyliczoną dla siebie długość po zmianie współrzędnych któregoś ze swoich punktów można zrealizować dodając do gettera właściwości ‘Dlugosc’ ciało metody ‘ObliczDlugosc()’. Jednak na potrzeby zapoznania się ze zdarzeniami, aktualizacja długości odcinka poprzez metodę obsługi zdarzenia zmiany współrzędnych punktów, wydaje się bardziej intuicyjna.

To co warte uwagi dzieje się w konstruktorze

            a.OnZmianaWspolrzednychPunktu += this.OnZmianaWspolrzednych;
            b.OnZmianaWspolrzednychPunktu += this.OnZmianaWspolrzednych;

Te linie można przeczytać tak. Do delegata ‘OnZmianaWspolrzednychPunktu’ punktu ‘a’ odcinek dodaje swoją metodę ‘OnZmianaWspolrzednych’ w której będzie reagował na wystąpienie takiego zdarzenia. Drugą linie można analogicznie przeczytać, z tą różnicą ze w drugiej linii odcinek zapisuje swoją metodę obsługi zdarzenia do delegata punktu ‘b’.

I tu wzmianka na temat słówka ‘event’ przy obiekcie delegata w klasie ‘Punkt’ i co to słówko daje.

Usuwając to słówko z pola w klasie ‘Punkt’ i zachowując klasy w kształcie jak teraz nic się nie zmieni, wszystko będzie działało. Jedyną różnicą którą można wprowadzić w kodzie klasy ‘Odcinek’ gdy pole delegata w klasie ‘Punkt’ nie jest oznaczone słowem ‘event’ jest zamiana operatora ‘+=’ na ‘=’.

            a.OnZmianaWspolrzednychPunktu = this.OnZmianaWspolrzednych;
            b.OnZmianaWspolrzednychPunktu = this.OnZmianaWspolrzednych;

I to jest dramat. Dramat polega na tym, że o ile operatorem ‘+=’ dany odcinek grzecznie dodawał w konstruktorze swoją metodę do delegata ‘OnZmianaWspolrzednychPunktu’ obu punktów. To w przypadku użyciu operatora ‘=’ dzieje się najzwyklejsze przypisanie do delegata ‘OnZmianaWspolrzednychPunktu’ metody danego odcinka. Przypisanie, to znaczy że wszystkie poprzednio zapisane metody w delegacie punktu przez inne odcinki zostały utracone i od chwili użycia operatora ‘=’ w delegacie zostało tylko to co było po prawej stronie operatora ‘=’ w chwili przepisania, nic więcej. Widać ze delegat bez słowa ‘event’ jest jak upublicznione pole klasy które powinno być tylko do odczytu z dostępem tylko z wnętrza klasy a zostało wystawione jako publiczne.
Hermetyzację tego pola delegatu daje zastosowania słowa ‘event’ przed delegatem. Od momentu określenia delegata jako ‘event’ zabronione jest używanie operatora przypisania ‘=’ do tego delegata przez inne obiekty. Wiec obce obiekty (m.in. odcinki) nie maja już prawa jedną linią skasować całej zawartości delegata a jedynie mogą wpisywać i wypisywać pojedyncze metody do delegata.

Drugą wartą uwagi rzeczą jest metoda ‘OnZmianaWspolrzednych’:

    public void OnZmianaWspolrzednych(Punkt sender, WspolrzedneArgs e)
    {
        Dlugosc = ObliczDlugosc();
    }

Metoda ta ma sygnaturę zgodną z delegatem jak niżej i dzięki temu może być do niego dodawana:

public delegate void ZmianaWspolrzednychHandler(Punkt sender, WspolrzedneArgs args);

Jednak metoda odcinka nie wykorzystuje danych przesłanych jako argumenty do tej metody przez delegat w momencie wywołania delegata w setterze właściwości X lub Y klasy Punkt (Punkt sender, WspolrzedneArgs args). Odcinek po prostu aktualizuje wyliczenie swojej długości i tyle, po co w takim razie przesyłamy w tym zdarzeniu te dane? O tym na końcu przykładu.

Wyjaśnienia wymaga jeszcze ciało metody ‘ObliczDlugosc()’, wykorzystano w niej oddzielną klasę statyczną wyliczającą, odległość pomiędzy dwoma punktami, wyliczającą obwód trójka na podstawie tylko współrzędnych wierzchołków trójkąta oraz wyliczającą pole trójkąta również tylko na podstawie współrzędnych wierzchołków.

Klasa WielkiMatematyk

    public static class WielkiMatematyk
    {
        public static double OdlegloscMiedzyPunktami(Punkt a, Punkt b)
        {
            double bokX = a.X - b.X;
            double bokY = a.Y - b.Y;

            return Math.Round(Math.Sqrt(Math.Pow(bokX, 2) + Math.Pow(bokY, 2)), 2);
        }

        public static double PoleTrojkataWzorHerona(Punkt a, Punkt b, Punkt c)
        {
            double wynik = 0;
            double bok_ab = OdlegloscMiedzyPunktami(a, b);
            double bok_bc = OdlegloscMiedzyPunktami(b, c);
            double bok_ac = OdlegloscMiedzyPunktami(a, c);
            double polowaObwodu = ObwodTrojkataZeWspolrzednychWierzcholkow(a, b, c) / 2;

            wynik = Math.Sqrt(polowaObwodu * (polowaObwodu - bok_ab) * (polowaObwodu - bok_bc) * (polowaObwodu - bok_ac));

            return Math.Round(wynik, 2);
        }

        public static double ObwodTrojkataZeWspolrzednychWierzcholkow(Punkt a, Punkt b, Punkt c)
        {
            double bok_ab = OdlegloscMiedzyPunktami(a, b);
            double bok_bc = OdlegloscMiedzyPunktami(b, c);
            double bok_ac = OdlegloscMiedzyPunktami(a, c);

            return Math.Round(bok_ab + bok_bc + bok_ac);
        }
    }

Klasa trójkąt nie zawiera w sobie niczego nowego poza to co zostało już omówione przy okazji klasy Odcinek.

Klasa Trójkąt

    public class Trojkat
    {
        public Punkt Wierzcholek_1 { get; private set; }
        public Punkt Wierzcholek_2 { get; private set; }
        public Punkt Wierzcholek_3 { get; private set; }

        public double Obwod { get; private set; }
        public double Pole { get; private set; }

        public Trojkat(Punkt a, Punkt b, Punkt c)
        {
            Wierzcholek_1 = a;
            Wierzcholek_2 = b;
            Wierzcholek_3 = c;
            a.OnZmianaWspolrzednychPunktu += this.OnZmianaWspolrzednychWierzcholkow;
            b.OnZmianaWspolrzednychPunktu += this.OnZmianaWspolrzednychWierzcholkow;
            c.OnZmianaWspolrzednychPunktu += this.OnZmianaWspolrzednychWierzcholkow;
            Obwod = ObliczObwod();
            Pole = ObliczPole();
        }

        public void OnZmianaWspolrzednychWierzcholkow(Punkt sender, WspolrzedneArgs e)
        {
            Obwod = ObliczObwod();
            Pole = ObliczPole();
        }

        private double ObliczObwod()
        {
            return WielkiMatematyk.ObwodTrojkataZeWspolrzednychWierzcholkow(Wierzcholek_1, Wierzcholek_2, Wierzcholek_3);
        }

        private double ObliczPole()
        {
            return WielkiMatematyk.PoleTrojkataWzorHerona(Wierzcholek_1, Wierzcholek_2, Wierzcholek_3);
        }

        public override string ToString()
        {
            return $"Trojkat o wierzcholkach ({Wierzcholek_1.Nazwa}),({Wierzcholek_2.Nazwa}),({Wierzcholek_3.Nazwa}), " +
                $"o obwodzie: {Obwod} i polu powierzchni: {Pole}";
        }
    }

Całość jest już gotowa, wszystko już może ze sobą współpracować, można już testować cały program ale dodajmy jeszcze jedną klasę. Niech to będzie klasa która nie będzie żadnym obiektem geometrycznym. Niech to będzie coś co będzie subskrybentem wszystkich zdarzeń, wszystkich punktów utworzonych w ramach całego programu. I niech te wszystkie wyłapane zdarzenia dokumentuje i zapisuje, takie zapisywanie logów (ale w tym przypadku nie do plików).
I niech ten obiekt po zakończeniu programu wypisze wszystko to co zarejestrował.

Klasa SkrybaZmian

    public class SkrybaZmian
    {
        public List<string> listaPrzechwyconychZmian = new List<string>();

        public void DoZapisania(Punkt sender, WspolrzedneArgs e)
        {
            listaPrzechwyconychZmian.Add(
                $"Punkt ({sender.Nazwa}). Zmienił wspolrzedną \"{e.ZmienianaWspolrzedna}\" z {e.Stara} na {e.Nowa}");
        }
    }

Ta klasa nie będąca figurą geometryczną również ma metodę o sygnaturze zgodnej z naszym delegatem. I to daje jej możliwość również bycia subskrybentem wszystkich zdarzeń zmiany współrzędnych punktów. Co prawda klasa ta nie ma żadnego jawnego konstruktora, żadne punkty nie są do niej przekazywane i klasa sama w sobie nie przypisuje swojej metody do delegatów zdarzeń punktów ale można takie przypisanie zrealizować w kodzie sprawdzającym jak poniżej.
Warto też zauważyć że klasa 'SkrybaZmian' jako jedyny wykorzystuje obiekty przesylane przy wywolywaniu delegata, 'sender' i 'e'. Tak naprawde tutaj widać po co wła_ciwie była tworzona klasa 'WspolrzedneArgs' i dlaczego warto ją dobrze zaprojektować.

Kod sprawdzający działanie w konsoli.
//wszystkie klasy należy umiescic w przestrzeni nazw tej samej w której jest klasa Program w default’owej aplikacji konsolowej.

SkrybaZmian skryba = new SkrybaZmian();

            Punkt a = new Punkt(3, 2);
            Punkt b = new Punkt(7, 5);
            Punkt c = new Punkt(7, 2);

            Odcinek odcinek1 = new Odcinek(a, b);
            Odcinek odcinek2 = new Odcinek(b, c);
            Odcinek odcinek3 = new Odcinek(a, c);

            Trojkat trojkat = new Trojkat(a, b, c);

            a.OnZmianaWspolrzednychPunktu += skryba.DoZapisania;
            b.OnZmianaWspolrzednychPunktu += skryba.DoZapisania;
            c.OnZmianaWspolrzednychPunktu += skryba.DoZapisania;

            Console.WriteLine(a);
            Console.WriteLine(b);
            Console.WriteLine(c);
            Console.WriteLine();
            Console.WriteLine(odcinek1);
            Console.WriteLine(odcinek2);
            Console.WriteLine(odcinek3);
            Console.WriteLine();
            Console.WriteLine(trojkat);
            Console.WriteLine();

            Console.WriteLine("Pkt_A, Nowa wspolrzedna X: ");
            int nowyX = Int32.Parse(Console.ReadLine());
            Console.WriteLine("Pkt_A, Nowa wspolrzedna Y: ");
            int nowyY = Int32.Parse(Console.ReadLine());

            a.X = nowyX;
            a.Y = nowyY;

            Console.WriteLine("Pkt_B, Nowa wspolrzedna X: ");
            nowyX = Int32.Parse(Console.ReadLine());
            Console.WriteLine("Pkt_B, Nowa wspolrzedna Y: ");
            nowyY = Int32.Parse(Console.ReadLine());

            b.X = nowyX;
            b.Y = nowyY;

            Console.WriteLine("Pkt_C, Nowa wspolrzedna X: ");
            nowyX = Int32.Parse(Console.ReadLine());
            Console.WriteLine("Pkt_C, Nowa wspolrzedna Y: ");
            nowyY = Int32.Parse(Console.ReadLine());

            c.X = nowyX;
            c.Y = nowyY;

            Console.WriteLine(a);
            Console.WriteLine(b);
            Console.WriteLine(c);
            Console.WriteLine();
            Console.WriteLine(odcinek1);
            Console.WriteLine(odcinek2);
            Console.WriteLine(odcinek3);
            Console.WriteLine();
            Console.WriteLine(trojkat);

            foreach (var item in skryba.listaPrzechwyconychZmian)
            {
                Console.WriteLine(item);
            }
Console.ReadLine();

Przypisanie skryby jako subskrybenta zdarzeń do wszystkich punktów odbywa się w miejscu:

            a.OnZmianaWspolrzednychPunktu += skryba.DoZapisania;
            b.OnZmianaWspolrzednychPunktu += skryba.DoZapisania;
            c.OnZmianaWspolrzednychPunktu += skryba.DoZapisania;

Cześć 2.

===================================================================================

Poniżej stary artykuł - autorstwa Degusto.

===================================================================================

Do czego służą zdarzenia?

Zdarzenia służą do powiadomienia użytkownika, gdy w używanej klasie dojdzie do pewnych wydarzeń zdefiniowanych przez twórcę klasy.
Możemy dzięki temu w odpowiedni sposób reagować np. jeśli wartość zmiennej została zmieniona lub jeśli użytkownik kliknął w przycisk.

Jak stworzyć zdarzenie?

Zdarzenia tworzymy używając delegatów. Więcej informacji tutaj: Delegaty. Definicja zdarzenia wygląda tak:

public event TypDelegata NazwaZdarzenia;

public delegate void WartoscZmienionaEventHandler(int obecnaWartosc);
public event WartoscZmienionaEventHandler WartoscZmieniona;

Rejestrowanie własnych metod dla zdarzenia

Własne metody rejestrujemy identycznie jak w przypadku delegatów(możemy je też wyrejestrować).

var foo = new Foo();
foo.WartoscZmieniona += foo_WartoscZmieniona;

Możemy też zamiast metody zarejestrować po prostu delegata:

var foo = new Foo();
var delegat = new WartoscZmienionaEventHandler(foo_WartoscZmieniona);            
foo.WartoscZmieniona += delegat;

Przykład

using System;

namespace Zdarzenia
{
    class Program
    {
        static void Main(string[] args)
        {
            var test = new TestFoo();
            test.Testuj();
            Console.ReadKey();
        }
    }

    class TestFoo
    {
        public void Testuj()
        {
            var foo = new Foo();
            foo.WartoscZmieniona += foo_WartoscZmieniona;
            foo.Wartosc = 5;
            foo.Wartosc = 6;
        }

        private void foo_WartoscZmieniona(int obecnaWartosc)
        {
            Console.WriteLine("Nowa wartość to: {0}", obecnaWartosc);
        }
    }

    delegate void WartoscZmienionaEventHandler(int obecnaWartosc);

    class Foo
    {
        public event WartoscZmienionaEventHandler WartoscZmieniona;
        private int _wartosc;
        public int Wartosc
        {
            get
            {
                return _wartosc;
            }
            set
            {
                _wartosc = value;
                if (WartoscZmieniona != null)
                {
                    WartoscZmieniona(_wartosc);
                }
            }
        }
    }
}

Delegaty vs Zdarzenia

Niektórym mogłoby się wydawać, że poza słówkiem event nie ma żadnej różnicy żadnej różnicy między delegatem, a zdarzeniem.
Jednak zdarzenia są pewnego rodzaju opakowaniem dla delegatów, tak jak właściwości dla pól. Dzięki temu zdarzenia mogą być częścią interfejsu.

Akcesory Add i Remove

Tak samo jak właściwości mają akcesory Get i Set tak samo zdarzenia mają Add i Remove. Powyższy przykład lekko zmodyfikowany:

using System;

namespace Zdarzenia
{
    class Program
    {
        static void Main(string[] args)
        {
            var test = new TestFoo();
            test.Testuj();
            Console.ReadKey();
        }
    }

    class TestFoo
    {
        public void Testuj()
        {
            var foo = new Foo();
            foo.WartoscZmieniona += foo_WartoscZmieniona;
            foo.Wartosc = 5;
            foo.Wartosc = 6;
        }

        private void foo_WartoscZmieniona(int obecnaWartosc)
        {
            Console.WriteLine("Nowa wartość to: {0}", obecnaWartosc);
        }
    }

    delegate void WartoscZmienionaEventHandler(int obecnaWartosc);

    class Foo
    {
        private event WartoscZmienionaEventHandler _wartoscZmieniona;
        public event WartoscZmienionaEventHandler WartoscZmieniona
        {
            add
            {
                if (value != null)
                {
                    _wartoscZmieniona += value;
                }
            }
            remove
            {
                if (value != null)
                {
                    _wartoscZmieniona -= value;
                }
            }
        }

        private int _wartosc;
        public int Wartosc
        {
            get
            {
                return _wartosc;
            }
            set
            {
                _wartosc = value;
                if (_wartoscZmieniona != null)
                {
                    _wartoscZmieniona(_wartosc);
                }
            }
        }
    }
}

Więcej informacji o delegatach i zdarzeniach

Rozdział 6
https://msdn.microsoft.com/en-us/library/aa645739(v=vs.71).aspx

C#

1 komentarz

Dla amatora przykłady ledwo czytelne... przydałyby się jakieś komentarze i łopatologia. Ale ogólnie może być