Archiwum kategorii: Techniczny inside

Odległość Levenshteina

Wstęp

Czy zastanawialiście się kiedyś, skąd wyszukiwarki „wiedzą”, co użytkownik miał na myśli pomimo popełnionych literówek w wyszukiwanym haśle? Odpowiedzią są algorytmy obliczające miarę odmienności napisów. W tym artykule, postaram się omówić jeden z nich, jak również spróbować zoptymalizować go tak, aby był bardziej użyteczny dla rozwiązań informatycznych.

Odległość Levenshteina

Odległość Levenshteina – bo o tym algorytmie dzisiejszy wpis będzie – jest to prosty algorytm wyznaczający miarę odmienności napisów, czyli w uproszczeniu wyznacza ilość operacji prostych, które muszą zostać wykonane, aby słowo A stało się słowem B. Operacja prosta, w tym przypadku to:

• wstawienie nowego znaku
• usunięcie znaku
• podmienienie znaku

Dla zobrazowania, aby napis „kot” stał się napisem „mol” potrzebujemy wykonać 2 operacje proste – zamienić literkę „k” na „m” oraz „t” na „l”. Więc miara odmienności słowa „kot” i „mol” jest równa 2.

Algorytm

Jak więc nauczyć komputer, aby też potrafił rozpoznawać tę różnicę w słowach? Władimir Lewensztejn zaproponował poniższy algorytm:

1. Określamy długość badanych słów, długość słowa głównego A to `m`, a długość słowa porównywalnego B to `n`.

2. Tworzymy macierz(tablicę) `K` o wymiarach (m+1) x (n+1) .

3. Pierwszy wiersz macierzy wypełniamy wartościami od 0 do `n`

4. Pierwszą kolumnę macierzy wypełniamy wartościami od 0 do `m`

5. Dla każdego znaku słowa A wykonaj (od 1 do `m`, ozn. Indeksu `i`):

6. Dla każdego znaku słowa B wykonaj (od 1 do `n`, ozn. Indeksu `j`):

◦ jeżeli `A[i]` jest taki sam tak `A[j]` to koszt jest równy zero, w przeciwnym wypadku koszt jest równy 1
◦ przypisujemy wartość pola macierzy `K[i][j]` obliczając minimalną wartość z:
▪ `K[i – 1][j – 1]` + koszt
▪ `K[i – 1][j]` + 1
▪ `K[i][j – 1]` + 1

7. Miarą odmienności jest wartość wpisana w `K[m][n]`

Stosując powyższy algorytm, przeanalizujmy słowa „kot” i „pole”:

1. Długość słów m 3, n 4

2. 3. 4. Tworzymy macierz z wypełnionymi wartościami.

1

5. Obliczamy wartości macierzy.

2

Znak P różni się od K więc koszt jest równy 1. Obliczamy wartości:

• `K[2 – 1][2]` + 1 = 2

• `K[2][2 – 1]` + 1 = 2

• `K[2 – 1][2 – 1]` + 1(koszt) = 1

Najmniejsza wartość to 1, więc tyle wpisujemy w pole `K[2][2]`.

Podobnie uzupełniamy resztę wartości z wiersza.

3

W tym przypadku znaki się od siebie nie różnią, więc koszt jest równy 0. Więc:

• `K[3 – 1][3]` + 1 = 3

• `K[3][3 – 1]` + 1 = 3

• `K[3 – 1][3 – 1]` + 0 = 1

I wpisujemy w pole `K[3][3]` wartość 1.

4

Implementacja

Implementacja algorytmu jest wkompilowana w interpreter PHP od wersji 4.0.1. Na potrzeby testów i optymalizacji zaimplementowałem go i można znaleźć go w repozytorium: https://gitlab.com/dysan12/levenshtein_distance

Optymalizacja

Wykonując krokowo algorytm, możemy zauważyć pewną zależność, dzięki której nie musimy wykonać wszystkich obliczeń, aby szacować odmienności podanych napisów. Wystarczy, że przy graficznej interpretacji „poprowadzimy” ukośną linię z wynikowego narożnika macierzy.

5

W tym wypadku, już po uzupełnieniu pierwszego wiersza, wiemy że odmienność napisów, będzie wynosiła co najmniej 2.

Szacunek ten możemy przykładowo wykorzystać w sytuacji, gdy chcemy poznać wszystkie słowa z pewnego zbioru, których odmienność od badanego słowa nie będzie większa od zadanej.

Implementacja

Przyjmując kontynuację algorytmu krokowego oraz , że `max` to zadana maksymalna różnica słów:
Dodajemy do każdej iteracji kroku piątego warunki kończące wykonywanie

• jeśli porównywane słowa mają taką samą długość, to sprawdź czy wartość `K[i][j]` > `max`
• jeśli słowo porównywane B jest dłuższe niż słowo główne A, to sprawdź czy wartość `K[i][i + n – m]` > `max`
• jeśli słowo porównywane B jest krótsze niż słowo główne A, to sprawdź czy wartość `K[i][i – (n – m)]` > `max`

Jeśli algorytm przedwcześnie zakończył swoją pracę, tzn. że badane słowa zbyt różnią się od siebie.

40080793_352743705265754_3288724306145574912_n

Ilustracja 1: Optymalizacja zrealizowana w PHP

Wyniki

Testy zostały przeprowadzone na ponad 1000 elementowym zbiorze porównywanych słów z wyznaczoną maksymalną odległością od 0 do 10.

Aspekty:

• zoptymalizowany algorytm vs algorytm bez optymalizacji
• zoptymalizowany algorytm vs wbudowany w PHP(bez optymalizacji)

Gdzie:

• „Optimized algorithm time” to czas wykonania zoptymalizowanego algorytmu
• „Standard algorithm time” to czas wykonania algorytmu bez optymalizacji
• „Saved time” to zaoszczędzony czas
• „Percentage ratio” to stosunek czasów. Jeśli wartość jest mniejsza niż 100% to zoptymalizowany algorytm wykonywał się dłużej.

Zoptymalizowany algorytm vs algorytm bez optymalizacji

6

Z wyników widzimy, że optymalizacja, przy niskiej zadanej maksymalnej odległości, polepsza do 212% wydajność co daje ponad dwukrotnie skrócony czas obliczania.

Zoptymalizowany algorytm vs wbudowany w PHP(bez optymalizacji)

7

Wbudowana funkcja obliczania odległości levenshteina jest skompilowana w interpreter PHP, przez co wydajnościowo jest niejednokrotnie ponad 80 razy szybsza niż nieskompilowany algorytm – co nie było zaskoczeniem.

Wszystkie testy wraz z badanym zbiorem danych znajdują się w
https://gitlab.com/dysan12/levenshtein_distance/blob/master/test/LevenshteinTest.php

Podsumowanie optymalizacji

Z przeprowadzonych testów widzimy, że optymalizacja rzeczywiście skraca czas wykonania algorytmu, lecz aby z niej w praktyce skorzystać w języku interpretowanym, musielibyśmy wkompilować ją w interpreter.

Autorem tekstu jest Michał Gaj.

ANTLR

ANTLR – ANother Tool for Language Recognition

Na pewno spotkałeś się kiedyś z niestandardowymi strukturami danych, które ciężko przetworzyć w konwencjonalny sposób. W tym wpisie postaram się pokazać jak możesz poradzić sobie z takim problemem.

Czym jest ANTLR?

ANTLR – to generator parsera do czytania, wykonywania lub tłumaczenia binarnych oraz tekstowych struktur danych. Standardowy projekt procesowania danej struktury danych składa się z pliku Grammar zawierającego reguły dla Lexera oraz Parsera, które zaś używane są do tokenizacji oraz przetwarzania danych według wcześniej stworzonych reguł.

Ciekawostka

Zastanawiałeś się kiedyś jak działa analiza składni, dla przykładu w Netbeans? Jeśli tak, to pewnie nie zaskoczę Cię informacją, że używany jest tam między innymi ANTLR do analizy składni języka C++.

Ponad to, znany pewnie wszystkim Twitter, używa ANTLR do przetwarzania zapytań dla wyszukiwania. Są to ponad 2 miliardy zapytań na dzień. Ilość robi wrażenie? Na mnie tak.

Do czego możemy użyć ANTLR w praktyce?

Do wielu rzeczy, począwszy od przetwarzania niestandardowych strukturalnie plików konfiguracyjnych, przez konwertery tzw. ,,legacy code”, po różnego rodzaju parsery, renderowanie znaczników i tym podobne. Możliwości jest na prawdę sporo.

W jakich językach programowania można tego użyć?

W zasadzie we wszystkich najpopularniejszych językach: Java, C#, C++, Python, Swift, Go (po za PHP, chociaż swego czasu widziałem gdzieś, zrobiony port dla tego języka). Dla każdego z tych języków ,,kompilator” gramatyczny wygeneruje parser oraz lexer do przetwarzania zdefiniowanej przez nas struktury danych.

Przykład użycia

Na potrzeby tego wpisu, językiem programowania przy użyciu, którego będę prezentować możliwości ANLTR to C# (miłośnicy Javy muszą mi wybaczyć 🙂 ). Natomiast samo narzędzie ANLTR wykorzystam w jego najnowszej wersji, na dzień tworzenia tego wpisu czyli V4.

*Procesowanie przykładowej prostej struktury danych:

bq. # Comment
TypeId = 860
Name        = "Example Item"
Description = "Description of example item"
Attributes  = {Waypoint=0, Class=1}
Flags       = {Block, Pickable}

Struktura dosyć prosta, zapewne można by bez większego problemu przetworzyć takie dane przy użyciu choćby regexów. Jednak, chciałbym na tym dosyć trywialnym przykładzie pokazać jak działa ANTLR.

Plik grammar (.g4) wygląda w ten sposób:

grammar Objects;
 
root:
    items+
    ;
 
items:
    ('TypeId' '=' typeId) ('Name' '=' name) ('Description' '=' description)? ('Attributes' '=' attributes)? ('Flags' '=' flags)?
    ;
typeId:
     NUMBER
    ;
name:
    TEXT
    ;
description:
    TEXT
    ;
flags:
    ('{') flag+ ('}')
    ;
flag:
    STRING
    ;
attributes:
    ('{')  attribute+ ('}')
    ;
attribute:
    attributeName STRING?'=' attributeValue
    ;
attributeName:
    STRING;
attributeValue:
    NUMBER;
 
fragment DIGIT:
   ('-')? '0'..'9'
;
 
TEXT:
    '"' .*? '"'
;
 
STRING:
    ('a'..'z' | 'A'..'Z')+
;
 
NUMBER:
    DIGIT+
;
 
WHITESPACE:
    ' ' -> skip
;
 
COMMA_SEPARATOR :
    ',' -> skip
;
 
NEWLINE :
    ('\n' | '\r' | '\r\n') -> skip
;
 
COMMENT     :
    '#' ~( '\r' | '\n' )* -> skip
    ;

Kompilator na podstawie gramatyki zawartej w pliku grammar utworzy w docelowym języku (w naszym przypadku C#) parser oraz lexer przy użyciu których możemy skorzystać z możliwości przetworzenia wskazanych danych.

t1

Powstałe drzewo wygląda dosyć prosto i przejrzyście. Oczywiście w przypadku większej ilości danych, drzewo rozszerzy się o kolejne elementy.

t2

Jak z wydajnością takiego rozwiązania? To zależy od skomplikowania danych, ale mogę z czystym sumieniem powiedzieć, że w większości przypadków, przy odpowiednio stworzonej gramatyce, wydajność jest na zadowalającym poziomie.

Przykładowa klasa Reader

using Antlr4.Runtime;
using Objects.Antlr;
using Antlr.Visitor;
using Objects.Domain;
 
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
 
namespace Objects
{
    class ObjectsReader
    {
        private readonly string objectsFilePath;
 
        public ObjectsReader(string objectsFilePath)
        {
            this.objectsFilePath = objectsFilePath;
        }
 
        public List Read()
        {
            var objectsFileContent = System.IO.File.ReadAllText(objectsFilePath);
 
            var inputStream = new AntlrInputStream(objectsFileContent);
            var objectsLexer = new ObjectsLexer(inputStream);
            var commonTokenStream = new CommonTokenStream(objectsLexer);
 
            var objectsParser = new ObjectsParser(commonTokenStream);
            var itemsContext = objectsParser.root().items();
 
            var visitor = new ItemVisitor();
 
            foreach(var itemContext in itemsContext)
            {
                visitor.Visit(itemContext);
            }
 
            // Do something with parsed item's
        }
    }
}

Klasa Visitor z implementacją interfejsu z biblioteki ANTLR. Visitor służy do procesowania określonej części powstałego drzewa, danej struktury danych.

using Antlr4.Runtime.Misc;
using Objects.Domain;
 
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
 
namespace Antlr.Visitor
{
    class ItemVisitor : ObjectsBaseVisitor
    {
        public override Item VisitItems([NotNull] ObjectsParser.ItemsContext context)
        {
            var typeId = context.typeId().GetText();
            var name = context.name().GetText();
            var flags = context.flags();
            var description = context.description();
            var attributes = context.attributes();
 
            // Container
            var Item = new Item {
                Id = Convert.ToInt32(typeId),
                Name = name
            };
 
            if (null != description)
            {
                var descriptionText = description.GetText();
                Item.Description = descriptionText;
            }
 
            if (null != flags)
            {
                var _flags = flags.flag();
 
                foreach (var _flag in _flags)
                {
                    var flagText = _flag.GetText();
 
                    try
                    {
                        ItemFlag itemFlag = (ItemFlag)Enum.Parse(typeof(ItemFlag), flagText);
                        Item.AddFlag(itemFlag);
                    }
                    catch(ArgumentException)
                    {
                        Console.WriteLine("Undefined flag: {0}", flagText);
                    }
                }
            }
 
            if (null != attributes)
            {
                var _attributes = attributes.attribute();
 
                foreach(var _attribute in _attributes)
                {
                    var attributeType = _attribute.attributeName().GetText();
                    var attributeValue = _attribute.attributeValue().GetText();
 
                    try
                    {
                        ItemAttributeType itemAttributeType = (ItemAttributeType)Enum.Parse(typeof(ItemAttributeType), attributeType);
                        Item.AddAttribute(new ItemAttribute(itemAttributeType, Convert.ToInt32(attributeValue)));
                    }
                    catch (ArgumentException)
                    {
                        Console.WriteLine("Undefined attribute: {0}", attributeType);
                    }
                }
            }
 
            // Do something with parsed data
 
            return base.VisitItems(context);
        }
    }
}

Kod w klasach jest jedynie przykładem użycia parsera niżeli działającą wersją.

Po więcej informacji zapraszam na stronę projektu: http://www.antlr.org/index.html

Autorem tekstu jest Miłosz Lenczewski.

JMeter – praktycznie zastosowanie

Część ogólna

Apache JMeter jest narzędziem do przeprowadzania testów obciążeniowych (wydajnościowych). Testy te są bardzo ważną częścią każdego serwisu – pozwalają oszacować jak duży ruch jest w stanie obsłużyć nasz serwer www, baza danych itp. Niżej wymieniony wpis będzie dotyczył wykorzystania JMetera do przeprowadzania testów obciążeniowych np. aplikacji www. Wersja programu – 3.1. Każda z niżej wymienionych opcji dostępna jest po kliknięciu prawym klawiszem myszy.

Na początku musimy zdefiniować grupę wątków. Znajdziemy go w Test plan → Add → Threads (Users) → Thread group

Grupa wątków służy do zarządzania ilością żądań, które będą wysyłane w ciągu jednej sekundy:

Number Of Threads – ilość wątków używanych do wysyłania żądań,
Ramp-Up Period – w ciągu ilu sekund mają zostać wysłane żądania,
Loop Count – ile razy dany test ma się powtórzyć,
Delay Thread creation until needed – bez oznaczonej tej opcji, JMeter przydziela od razu całą pamięć dla wszystkich wątków. Oznacza to, że nawet jeżeli użytkownik wykona swoją próbkę po np. 30 minutach to pamięć będzie dla niego zarezerwowana od razu po uruchomieniu skryptu
Scheduler – możemy ustawić w jaki dzień i o której godzinie chcemy uruchomić test.

Dla przykładu, jeżeli podamy number of thread na 100, ramp-up period na 10, wtedy na każdą sekundę będzie wysyłanych 10 użytkowników (żądań).

t1

Następnym krokiem będzie skonfigurowanie testu dla wcześniej utworzonej grupy wątków. Jako, że żądania JMetera są ubijane przez wiele serwisów (brak odpowiedniej identyfikacji), na początku należy skonfigurować nagłówek z User-Agent. Znajdziemy go w Thread Group → Add → Config Element → HTTP Header Manager.

t2

Kolejnym krokiem będzie zdefiniowanie domyślnego adresu url. Jest to o tyle ważne, że usprawnia proces zmiany głównej ścieżki. Może zmienić się nazwa domeny z np. teststrona.pl na testowy.pl. Jeżeli nie przygotowalibyśmy domyślnego adresu należałoby zmienić adres w każdym kroku testu gdzie wykonujemy żądania na sprecyzowany adres url np. test.pl/home. Znajdziemy go w Thread Group → Add → Config Element → HTTP Request Defaults.

t3

Jeżeli chcemy, aby każde żądanie pobierało nam dodatkowo zawartość dynamiczną strony (zdjęcia, js itp.), należy w HTTP Request Default lub w każdym HTTP Request z osobna, oznaczyć Retrieve All Embedded Resources. Parallel Downloads wskazuje ile ma być równolegle pobieranych zasobów, a URLs must match wskazujemy z jakiego adresu url chcemy pobierać zasoby.

t4

Dla przykładu wysłałem dwa żądania na ten sam adres url. Jeden pobiera zawartość dynamiczną a drugi statyczną:

t5

Następnie definiujemy Random Controller. Dodajemy go w celu zwiększenia losowości wykonywanego testu. Po jego użyciu wybierana jest tylko jedna akcja z kilku dostępnych w ramach danego wątku w określonym teście. Thread Group → Add → Logic Controller → Random Controller.

t6

Dla Random Controllera dodajemy HTTP Request. Służą one do wskazywania na jaki adres url mają być wysyłane żądania. W poprzednim kroku zdefiniowaliśmy HTTP Request Default, więc teraz pozostaje wskazać już tylko ścieżkę docelową. Thread Group → Add → Sampler → HTTP Request

t7

Dodatkowo w tym miejscu możemy definiować:

protocol – jaki to jest protokół tzn http/https,
method – jaką metodą ma być wysyłane żądanie dla zasobu (GET, POST itp.),
path – podajemy tutaj naszą docelową ścieżkę,
parameters/body data – możemy przekazać dodatkowe parametry/dane

Bardzo ważnym elementem są również słuchacze. Zbierają one różne dane o próbkach (żądaniach), w zależności od wybranego elementu. Oczywiście można wybrać każdy z nich: Thread Group → Add → Listener → View Results in Table

t8

Należy pamiętać, że dużym ograniczeniem dla JMeter-a są zasoby oraz łącze. Podczas działania testu zużywane są spore zasoby pamięci ram oraz łącza.

Uruchamianie JMeter-a poza GUI

Komendą do uruchamiania JMeter-a poza GUI jest:

sh jmeter -n -t /home/dawid/testObciazeniowy.jmx

Aby uruchomić test tą komendą musimy znajdować się w katalogu gdzie zainstalowaliśmy Apache JMeter → bin

Bardzo ważne są domyślne ograniczenia Javy, które wynoszą raptem 1 GB pamięci ram. Z doświadczenia wiem, że jest to bardzo mało, jeżeli chcemy zasymulować duży ruch. W przypadku gdy chcemy ustawić większą ilość pamięci dla procesów Javy wykonujemy nasz skrypt z dodatkowymi parametrami:

JVM_ARGS="-Xms13000m -Xmx13000m -XX:NewSize=13000m -XX:MaxNewSize=13000m" && export JVM_ARGS && sh jmeter -n -t /home/dawid/testObciazeniowy.jmx

Dodatkowo nie uruchamiając testu poprzez GUI możemy otrzymać jego wynik w pliku. Należy do naszej komendy dodać jeszcze jeden parametr. Finalna komenda wygląda następująco:

JVM_ARGS="-Xms13000m -Xmx13000m -XX:NewSize=13000m -XX:MaxNewSize=13000m" && export JVM_ARGS && sh jmeter -n -t /home/dawid/testObciazeniowy.jmx -l /home/dawid/wynikTestu.csv

Uruchomienie skryptu – widok terminal:

t9

Wynik w postaci pliku csv:

t10

Wyniki testu

Poniżej przedstawię wyniki testu, który przeprowadzałem na jednym z naszych sklepów. Narzędzie wykorzystane do rejestrowania ruchu to relic. Jak widać na screenie ruch wyrażony jest w rpm czyli „requests per minute” (żądania na minutę):

t11

Podsumowanie testu w pliku prezentuje się w następujący sposób:

t12

Screen jest podglądowy, ogółem wszystkich wierszy jest dużo więcej.

Autorem tekstu jest Dawid Adamczyk.

Ferie z programowaniem

W połowie lutego, rozpocznie się zimowy kurs ,,Ferie z programowaniem”, organizowany przez i-systems. Przez cały tydzień uczestnicy będą zdobywali i szlifowali wiedzę z wybranych przez siebie tematów. Kurs skierowany jest do osób z podstawową wiedzą z zakresu programowania. Stawiamy na osoby ambitne, pracowite i takie, dla których kodzenie to pasja.

Termin i miejsce

Kurs odbędzie się w dniach: 12.02 – 16.02.2018 w Gliwicach, przy ulicy Bojkowskiej 47. Zajęcia będą trwały od godziny 8:00 do 16:00. Zapowiada się 5 intensywnych dni. 😉

Tematy do wyboru

  • Optymalizacja, wyszukiwanie wąskich gardeł w kodzie PHP
  • MySQL
  • Wzorce projektowe
  • Testy jednostkowe
  • Zarządzanie wersjami / composer
  • Debugging
  • Webservice’s
  • SPL
  • Domain Driven Design
  • Programowanie zorientowane obiektowo
  • SOLID
  • Hello world
  • IDE
  • SOLR
  • Testy Behat
  • CQRS
  • ES – Event Sourcing

Sylwetki prowadzących kurs:

Adam – programista PHP, od 3 lat pracujący komercyjnie, specjalizuje się w optymalizacji aplikacji

Łukasz – programista z 8-letnim stażem, specjalista w dziedzinie wzorców projektowych oraz testowania oprogramowania

Korneliusz – programista PHP z 10-letnim stażem, specjalizuje się w algorytmach, optymalizacji oraz integracji sklepów

Konrad – szef działu IT w i-systems, od 3 lat zarządza zespołem i projektami w IT

Tomek – programista PHP oraz Team Leader, w zawodzie od 6 lat, specjalizuje się w analizie i penetracji kodu oraz integracji z systemami zewnętrznymi.

Patrycja – testerka, pracuje z językiem PHP, od pół roku związana z testami automatycznymi, frameworkiem Behat oraz procesami TDD i BDD.

Korzyści z udziału w kursie:

  • Możliwość pracy w i-systems dla najlepszych uczestników
  • Zdobycie praktycznych umiejętności w zakresie podstaw programowania
  • Uzyskanie certyfikatu ukończenia kursu
  • Poznanie specyfiki pracy w IT
  • Wymiana wiedzy z innymi uczestnikami kursu
  • Nauka w kameralnym gronie

Rekrutacja

Poniżej znajduje się kilka kroków, które należy wykonać, aby wziąć udział w rekrutacji. Oto one:

  1. Przeczytaj ogłoszenie z ofertą kursu: http://bit.ly/2CRFUU2
  2. Wyślij poprzez formularz swoje CV* oraz list motywacyjny, w którym opiszesz/zakodujesz/narysujesz, dlaczego to właśnie Ty powinieneś wziąć udział w kursie.
  3. Termin nadsyłania aplikacji mija 21.01.2018 r. Wszystkie zgłoszenia po terminie, nie zostaną rozpatrzone. Grunt to trzymać się dedlajnu. 😉
  4. Czekaj na nasz telefon w sprawie umówienia się na spotkanie (6 lutego zostanie wybrana ostateczna lista uczestników, których poinformujemy o szczegółach).
  5. Wybierz 10 interesujących Cię tematów z listy zaproponowanych.
  6. Przyjdź na kurs 12 lutego, o godzinie 8:00 do siedziby i-systems.

*Osoby, które brały udział w kursie “Wakacje z programowaniem” nie mogą wziąć udziału w procesie rekrutacji do tego kursu.

Ferie z programowaniem to idealna okazja i szansa dla młodych osób, które swoją przyszłość wiążą z branżą IT. Uczestnictwo w kursie niesie za sobą wiele korzyści, dlatego zachęcamy do wzięcia udziału w procesie rekrutacji.

Zdarzenia i delegaty w języku C#

Dzisiejszy wpis będzie poświęcony dwóm bardzo interesującym mechanizmom języka C# – zdarzenia i delegaty.

Delegaty

Delegat w rozumieniu języka programowania to wskaźnik na funkcję – pojęcie znane z takich języków jak C++ czy Java. Jednak w odróżnieniu od innych języków w C#, mechanizm ten został uproszczony i łatwiejszy w użyciu. Delegaty umożliwiają późne wiązanie, czyli decyzja o utworzeniu obiektu zapada w trakcie działania programu. Przeanalizujmy przykład takiego delegata.

1

Tworzenie delegata odbywa się jak tworzenie zwykłej metody, przy czym należy zaznaczyć, że tworzymy delegat za pomocą słowa kluczowego delegate. Tworzone delegaty nie mają implementacji. Sygnaturę delegata określają argumenty jakie przyjmuje oraz zwracany typ. Używając delegatów należy pamiętać, że delegaty to po prostu nowe typy danych, z tym że tworząc zmienne ich typów można przypisać do nich metody o odpowiadającej im sygnaturze. Poniżej przykład użycia delegata.

Deklaracja metod o takiej samej sygnaturze jak sygnatura naszego delegata:

2

Utworzenie zmiennych delegata wskazujących na wcześniej zadeklarowane metody:

4

Do delegata można przypisać więcej niż jedną metodę. Dzięki temu w momencie wywołania delegata zostaną wywołane wszystkie metody przypisane do niego:

5

Zdarzenia

Zdarzenia to elementy wykorzystywane bardzo często, są to akcje wykonywane w momencie zaistnienia pewnej sytuacji, np. ruch myszką, kliknięcie na przycisk, itp.. Zdarzenia w języku C# opierają się na wcześniej opisanych delegatach.

Aby zadeklarować zdarzenie należy użyć słówka kluczowego event:

6

W tym przypadku do zmiennej Click będzie można przypisywać metody zgodne z sygnaturą delegata EventHandler, nic nie stoi na przeszkodzie, aby użyć w tym miejscu własnego delegata, pamiętając o tym aby przypisywać do zmiennej metody (może ich być więcej niż jedna) o takiej samej sygnaturze jak nasz delegat. W momencie zajścia określonego zdarzenia (np. kliknięcie na przycisk) wywołane zostaną wszystkie metody przypisane do tego zdarzenia. Przypisanie metody do zdarzenia odbywa się dokładnie tak samo jak przypisanie metody do delegata:

7
8

Podsumowanie

Delegaty i zdarzenia to bardzo użyteczne mechanizmy języka C#. Za ich pomocą można, przede wszystkim zwiększyć czytelność kodu, ponieważ zmniejszamy ilość instrukcji warunkowych, przez co kod staje się bardziej zrozumiały. Pisząc aplikacje desktopowe (WinForms, WPF) delegaty i zdarzenia będą naszymi nieodzownymi towarzyszami, gdyż pisanie aplikacji okienkowych opiera się głównie na zdarzeniach.

Autorem tekstu jest Mateusz Łysień.