Artykuł

sxc.hu sxc.hu
kwi 19 2010
0

Obsługa zdarzeń w C# - delegaty i eventy

Obsługa zdarzeń, jest niezwykle ważna w każdym języku programowania. Pozwala ona na sprawne wykonywanie operacji związanych z aktualnym przetwarzaniem wykonywanym w aplikacji. Dla programisty, ważne jest aby ten proces przebiegał w sposób jak najbardziej automatyczny. Dlatego w dzisiejszym tekście, postaram się opisać delegaty oraz tworzone na ich podstawie zdarzenia (z ang. events).

Delegaty (delegacje)

Pierwszym z elementów, który chciałbym omówić są delegaty. W gruncie rzeczy, są one odpowiedzialne za łączenie zdarzeń, które mogą wystąpić w czasie działania aplikacji z odpowiednimi metodami je obsługującymi. Deklaracja delegatu, bardzo przypomina deklarację metod, z tym że należy tu zwrócić uwagę na słowo kluczowe delegate:

public delegate void MyDelegate(string sMsg);

Przykładowa deklaracja delegatu o jednym parametrze i typie zwracanym void. Deklaracja delegatu, może zostać utworzona w ciele wybranej klasy jak i poza ciałem jakiejkolwiek z klas.

Aby móc skorzystać z delegatu w sposób prawidłowy, należy stworzyć metodę (metody), która będzie wywoływana w momencie jego wykorzystania. Spójrzmy na przykład:

public delegate void MyDelegate(string sMsg);

public class Program
{
    private static MyDelegate m_oMyDelegate = null;
    
    public static void PrintLower(string sMsg)
    {
        Console.WriteLine(sMsg.ToLower());
    }

    public static void PrintUpper(string sMsg)
    {
        Console.WriteLine(sMsg.ToUpper());
    }

    static void Main(string[] args)
    {
        // 1 sposób dodawania metody do delegatu
        m_oMyDelegate += PrintLower;
        // 2 sposób dodawania metody do delegatu
        m_oMyDelegate += new MyDelegate(PrintUpper);
        // Wywołanie delegacji 
        m_oMyDelegate("Testowy String");
        Console.ReadKey();
    }
}

Przeanalizujmy powyższy kod. W linii 1 znajduje się definicja wcześniej wspomnianego delegatu. Następnie widzimy kod właściwiej klasy. W linii 5 widoczny jest pusty obiekt delegacji. Następnie, znajdują się dwie metody, które pasują do sygnatury delegacji. Ich typ zwracany to void oraz jeden argument będący stringiem. Sercem tej niezwyklej prostej aplikacji jest metoda main. W jej wnętrzu, przypisujemy dwie wcześniej utworzone metody do naszej delegacji . Następnie, w linii 24 wywołujemy delegację. Na ekranie powinny pojawić się dwie linie tekstu.

Ten niezwykle prosty przykład pokazuje jak łatwo tworzyć delegaty, metody od nich zależne i łączyć to wszystko razem. Prawdziwa siła tego mechanizmu, drzemie jednak w zdarzeniach.

Obsługa zdarzeń z wykorzystaniem delegacji

Zdarzenia są elementem, z którego korzystamy bardzo często, może nawet nie świadomie. Wyobraźmy sobie jakąkolwiek aplikację, do której obsługi wykorzystywana jest myszka. Jakiekolwiek kliknięcie lewym czy prawym przyciskiem myszy, często wywołuje jakąś akcję np. ukazanie menu kontekstowego. I to jest właśnie zdarzenie. Żeby w pełni zbudować model zdarzeniowy potrzebujemy tak naprawdę czterech elementów:

  • Delegacji
  • Klasy argumentów dziedziczącej z klasy EventArgs
  • Klasy posiadającej metody obsługujące zdarzenia
  • Klasy odpowiedzialnej za wykonywanie operacji i wywoływanie zdarzeń

Powyższa struktura oczywiście może wyglądać inaczej, jednak taki układ elementów jest często wykorzystywany przez programistów.

Rozważmy teraz przykład prostej aplikacji obsługującej zdarzenie rezerwacji biletu do kina. Żeby nie zaciemniać kodu, ograniczymy się tylko do jednej metody obsługującej zdarzenie, która będzie odpowiedzialna za logowanie transakcji.

Deklaracja delegacji

Zgodnie z ogólnie przyjętymi zasadami, delegaty używane do obsługi zdarzeń posiadają dwa argumenty:

  • Obiekt wysyłający
  • Obiekt argumentów

Dlatego nasz delegat będzie wyglądać następująco:

public delegate void ReservationDelegate(object oSender, EventArgs oEventArgs);

Klasa argumentów

Klasa argumentów, odpowiedzialna będzie za przechowywanie informacji związanych z określoną rezerwacją. Dlatego powinna posiadać następujące pola:

  • ReservationId
  • Name
  • Movie
  • Location
  • TicketPrice

Są to oczywiście podstawowe pola, które akurat ja uznałem, że są potrzebne.

public class ReservationArgs : EventArgs
{
    public string ReservationId { get; private set; }
    public string Name { get; private set; }
    public string Movie { get; private set; }
    public string Location { get; private set; }
    public double TicketPrice { get; private set; }
   
    public ReservationArgs(string sName,
        string sMovie, string sLocation, double dTicketPrice)
    {
        ReservationId = System.Guid.NewGuid().ToString();
        Name = sName;
        Movie = sMovie;
        Location = sLocation;
        TicketPrice = dTicketPrice;
    }
}

Klasa definiująca metodę obsługującą zdarzenia

Kolejnym krokiem, jest definicja klasy oraz metody logującej zdarzenie rezerwacji biletu. Przydatne tutaj okaże się wykorzystanie klasy argumentów, zdeklarowanej powyżej do przekazywania danych rezerwacji.

public class EventHandlerClass
{
    public void LogTransaction(object oSender, EventArgs oEventArgs)
    {
        ReservationArgs oReservationArgs = oEventArgs as ReservationArgs;
        Console.WriteLine("Złożono rezerwację o następujących danych:" +
            "\nId: " + oReservationArgs.ReservationId +
            "\nImię i nazwisko: " + oReservationArgs.Name +
            "\nNazwa filmu: " + oReservationArgs.Movie +
            "\nMiejsce: " + oReservationArgs.Location +
            "\nCena biletu: " + string.Format("{0:C}", 
            oReservationArgs.TicketPrice));
    }
}

Klasa obsługująca rezerwację biletu

Klasa ta, powinna zawierać obiekt zdarzenia, metodę rezerwacji biletu oraz metodę odpowiedzialną za wywoływanie zdarzenia zwrotnego:

public class Reservation
{
    public event ReservationDelegate ReservationDone;

    public void Reserve(string sName, string sMovie, string sLocation,
        double dTicketPrice)
    {
        ReservationArgs oReservationArgs = new ReservationArgs(sName, sMovie, 
            sLocation, dTicketPrice);
        FireReservationEvent(oReservationArgs);
    }

    public void FireReservationEvent(EventArgs oEventArgs)
    {
        if (null != ReservationDone)
        {
            ReservationDone(this, oEventArgs);
        }
    }
}

W linii 5, widzimy metodę obsługującą rezerwację biletu. Przekazywane do niej są wszystkie niezbędny parametry. Na ich podstawie, tworzony jest obiekt klasy ReservationArgs. Następnie wywołujemy prywatną metodę FireReservationEvent, która w sposób synchroniczny aktywuje wszystkie przypisane do zdarzenia metody. Przypisanie metod do zdarzenia nastąpi w klasie głównej.

Testy, testy, testy...

Czas napisać główną klasę, w której wykorzystamy wszystkie wcześniej utworzone elementy:

class Program
{
    static void Main(string[] args)
    {
        EventHandlerClass oEventHandlerClass = new EventHandlerClass();
        Reservation oReservation = new Reservation();
        // rejestracja metody obsługi zdarzenia
        oReservation.ReservationDone += oEventHandlerClass.LogTransaction;
        oReservation.Reserve("Jan Kowalski", "Super film", "Rząd R, miejsce 10", 
            20.00);
        Console.ReadKey();
    }
}

Przedstawiony powyżej kod, bardzo łatwo zinterpretować. Najpierw tworzymy obiekt klasy EventHandlerClass (5), która zawiera metody obsługujące zdarzenia. Następnie tworzymy obiekt klasy Rezerwacji (6), dodajemy metodę obsługującą zdarzenie (8), by na samym końcu wywołać metodę rezerwacji (9).

Przykładowy efekt może wyglądać tak:

Podsumowanie

Delegaty i zdarzenia, mogą czasem zdawać się niepotrzebne. Zwłaszcza jeśli tworzymy proste, nieskomplikowane aplikacje. Jednak mimo wszystko warto się nimi zainteresować, bo umożliwiają tworzenie czytelnego, logicznego i łatwo rozszerzalnego kodu i są zgodne z ideą szerzona przez platformę .NET.

Data ostatniej modyfikacji: 23.09.2014, 22:05.

Podoba Ci się ten wpis? Powiedz o tym innym!

Send to Kindle

Komentarze

blog comments powered by Disqus