Programowanie oparte na zdarzeniach oraz wykorzystanie mechanizmów funkcyjnych do budowy elastycznego oprogramowania.
Student demonstruje umiejętność implementacji modelu wydawca-subskrybent z wykorzystaniem zdarzeń (events) oraz delegatów. Zadanie ma na celu pokazanie, jak w nowoczesnym C# można odseparować logikę biznesową (zmiana ceny) od logiki reakcji (powiadomienia użytkownika) przy użyciu wyrażeń lambda. Student uczy się zarządzać listą subskrybentów oraz dynamicznie reagować na zmiany stanu obiektu.
Wyobraź sobie, że budujesz rdzeń aplikacji dla biura maklerskiego, który musi informować inwestorów o gwałtownych ruchach cen na giełdzie. Klasa bazowa "Giełda" posiada słownik aktywów, których ceny zmieniają się losowo w symulowanym procesie. Twoim najważniejszym zadaniem jest zdefiniowanie delegata "InformatorCenowy" oraz opartego na nim zdarzenia "CenaUleglaZmienie". Klient aplikacji może "podpiąć się" pod to zdarzenie, przekazując fragment kodu (wyrażenie lambda), który zostanie wykonany za każdym razem, gdy cena konkretnej spółki przekroczy ustalony limit. Dzięki takiemu podejściu, system giełdowy nie musi wiedzieć, co klienci robią z otrzymaną informacją – może to być wysyłka e-maila, zapis do bazy danych lub po prostu wyświetlenie alertu na ekranie. Model ten pozwala na zachowanie czystości kodu i wysokiej modułowości – klasy inwestorów nie są bezpośrednio powiązane z klasą giełdy. Twoim wyzwaniem jest zapewnienie, aby powiadomienie zawierało kompletne dane: nazwę aktywa, starą cenę oraz nową cenę. Program powinien symulować kilka "ticków" giełdowych i pokazywać reakcje różnych subskrybentów na te same zdarzenia. Finalnie zademonstruj mechanizm odpinania subskrypcji, aby pokazać, że inwestor może w dowolnej chwili przestać śledzić rynek. Jest to klasyczny przykład wykorzystania programowania reaktywnego w środowisku .NET.
using System; // 1. Definicja delegata (kontraktu) public delegate void NotifyDelegate(string text, decimal price); class Gielda { // 2. Definicja zdarzenia public event NotifyDelegate OnAlert; public void GenerujAlert(string akcja, decimal cena) { Console.WriteLine($"\n[SYSTEM] Rynek: {akcja} zmienia wartość na {cena:C}"); // 3. Bezpieczne wywołanie zdarzenia OnAlert?.Invoke(akcja, cena); } } class Program { static void Main() { Gielda g = new Gielda(); // 4. Subskrypcja przez Lambda g.OnAlert += (s, p) => { if (p > 100) Console.WriteLine($"ALARM INWESTORA 1: Sprzedaj {s}! (drogo: {p})"); }; g.OnAlert += (s, p) => Console.WriteLine($"LOG SERWISU: Odczyt {s} = {p}"); // Symulacja g.GenerujAlert("ORLEN", 65.20m); g.GenerujAlert("KGHM", 145.00m); } }
Zastosowanie delegatów do przekazywania logiki operacji matematycznych jako argumentów metod. Student uczy się odróżniać definicję operacji od mechanizmu jej wykonywania, co pozwala na budowę niezwykle elastycznych procesorów danych.
Budujesz uniwersalny silnik obliczeniowy, który w przyszłości ma obsługiwać tysiące różnych wzorów matematycznych. Zamiast pisać wielką instrukcję "switch" dla każdego działania (dodawanie, mnożenie, potęgowanie), musisz zaprojektować metodę "WykonajDzialanie(double a, double b, Operacja op)". Gdzie "Operacja" jest delegatem przyjmującym dwa double i zwracającym wynik. Twoim zadaniem jest zaimplementowanie kilku konkretnych metod (Suma, Roznica) oraz wywołanie ich poprzez stworzony mechanizm silnika. Co więcej, musisz wykazać, że do Twojego kalkulatora można dopisać nową funkcjonalność "w locie" (np. obliczanie średniej), nie dotykając kodu źródłowego głównej metody obliczeniowej. Taki model pracy jest podstawą do budowania wtyczek i rozszerzeń w dużych systemach inżynierskich. Program w konsoli powinien poprosić o dwie liczby, a następnie kolejno zaprezentować wyniki dla wszystkich zarejestrowanych operacji. Dzięki delegatom, Twój kod staje się "lekki" i bardzo łatwy w utrzymaniu. Zakończ zadanie refleksją nad tym, jak ten wzorzec ułatwia testowanie jednostkowe poszczególnych działań matematycznych.
Opanowanie pracy z gotowymi delegatami generycznymi Action i Func dostarczanymi przez platformę .NET. Student uczy się wykorzystywać te typy do szybkiej budowy systemów bez konieczności definiowania własnych nazw delegatów.
Systemy operacyjne generują miliony logów dziennie, ale różni administratorzy chcą widzieć te dane w różnych formatach (np. JSON, CSV lub czysty tekst). Twoim zadaniem jest napisanie klasy "LogManager", która posiada metodę ProcesujWiadomosc. Metoda ta powinna przyjmować tekst logu oraz dwa delegaty generyczne: jeden typu "Func<string, string>" (który służy do sformatowania nagłówka) oraz drugi typu "Action<string>" (który decyduje, co zrobić z gotowym logiem – np. wyświetlić go na zielono w konsoli lub zapisać do zmiennej). Musisz przygotować kilka zestawów takich delegatów i pokazać, jak za pomocą jednej zmiany parametru całkowicie zmienia się wyjście Twojej aplikacji. Wykorzystanie Action i Func skraca kod i czyni go bardziej standardowym dla innych programistów C#. W tym zadaniu skup się na estetyce prezentacji danych – niech jeden format dodaje datę i godzinę, a inny unikalny prefiks [CRITICAL]. To ćwiczenie uczy projektowania potoków przetwarzania danych (pipelines), gdzie każdy etap (formatowanie, akcja końcowa) jest wymienny. Program kończy pracę po wyświetleniu trzech różnych wersji tego samego logu systemowego.
Dogłębne zrozumienie mechanizmu "event" i bezpieczeństwa przy jego wywoływaniu. Student uczy się projektować klasy reaktywne, które powiadamiają otoczenie o zmianach fizycznych mierzonych parametrów (np. temperatury).
Projektujesz system bezpieczeństwa dla nowoczesnego biurowca klasy A. Sercem systemu jest klasa "CzujnikDymu", która co kilka sekund generuje odczyt poziomu zadymienia. Musisz zaimplementować publiczne zdarzenie o nazwie "AlarmPozarowy", które zostanie wywołane tylko wtedy, gdy poziom dymu przekroczy wartość 70 jednostek. Do tego zdarzenia powinny być podpięte różne moduły: "SystemZraszania", "PowiadomienieStraży" oraz "KomunikatGlosowy". Twoim zadaniem jest upewnienie się, że jeżeli nikt nie zasubskrybuje alarmu (np. serwis wyłączył moduły), program nie wyrzuci błędu przy próbie wywołania zdarzenia. Wykorzystaj nowoczesną składnię ze znakiem zapytania (Invoke?.), aby bezpiecznie obsłużyć brak subskrybentów. Symulacja w Main powinna pokazać rosnący poziom dymu: przy 20 jednostkach nic się nie dzieje, a przy 80 wszystkie moduły alarmowe powinny jednocześnie zareagować w konsoli. To zadanie uczy, jak budować hierarchie powiadomień bez tworzenia twardych powiązań między klasami (decoupling). Na koniec wyłącz (odsubskrybuj) system zraszania i sprawdź, czy straż pożarna nadal otrzymuje powiadomienie. Jest to kluczowe ćwiczenie z zakresu architektury systemowej.
Zastosowanie wyrażeń lambda jako filtrów do przeszukiwania kolekcji. Student uczy się wykorzystywać delegat Predicate<T> do tworzenia uniwersalnych metod filtrujących, co znacznie redukuje powtarzalność kodu w aplikacjach bazodanowych.
Pracujesz nad systemem rekrutacyjnym dla firmy z branży IT. Posiadasz listę kandydatów (klasa Kandydat z polami: Nazwisko, WynikEgzaminu, LataDoswiadczenia). Dział HR potrzebuje narzędzia, które pozwoli na błyskawiczne filtrowanie tej listy według zmieniających się kryteriów (np. "tylko osoby z doświadczeniem powyżej 5 lat", "tylko osoby z wynikiem powyżej 80%"). Zamiast pisać oddzielną metodę dla każdego filtra, musisz przygotować jedną uniwersalną metodę "FiltrujKandydatow", która jako parametr przyjmuje delagat typu Predicate<Kandydat>. Twoim zadaniem jest wywołanie tej metody w Main kilkukrotnie, przekazując różne wyrażenia lambda jako kryteria wyboru. Dzięki temu Twoje rozwiązanie jest niezwykle eleganckie – nowa reguła biznesowa zajmuje ułamek sekundy i nie wymaga zmian w logice filtrowania. Program powinien wyświetlić listę osób zakwalifikowanych do kolejnego etapu po zastosowaniu złożonego warunku (użycie operatora && w lambdzie). To ćwiczenie pokazuje, jak lambdy i delegaty zamieniają skomplikowane algorytmy w proste i czytelne polecenia. Finalnie porównaj czas i wysiłek potrzebny na napisanie filtrów tradycyjnych versus tych opartych na lambdach. Wyniki zaprezentuj w formie zestawienia porównawczego.
Opanowanie techniki rozszerzania istniejących typów (nawet tych wbudowanych w C#) o nową funkcjonalność bez ich modyfikacji. Student uczy się pisać kod "fluent API", który jest bardziej intuicyjny i czytelny dla użytkownika końcowego.
Pracujesz nad systemem analitycznym dla banku, który często musi przeliczać kwoty na różne waluty oraz sprawdzać, czy dany ciąg znaków jest poprawnym numerem konta. Zamiast tworzyć klasy narzędziowe typu "Helper", musisz "nauczyć" klasę double metody ToPln() oraz klasę string metody CzyToKonto(). Służą do tego metody rozszerzające, które są definiowane w statycznych klasach przy użyciu słowa kluczowego "this" przed pierwszym parametrem. Twoim zadaniem jest stworzenie takich metod i pokazanie ich użycia w konsoli tak, jakby były one naturalnymi częściami języka (np. 150.50.ToPln()). Taki sposób pisania kodu jest niezwykle popularny w nowoczesnym C# (praktycznie całe LINQ na tym bazuje). Program powinien sformatować kilka liczb jako kwoty walutowe oraz zweryfikować poprawność trzech testowych ciągów znaków (np. sprawdzenie długości i czy są same cyfry). To zadanie uczy, jak tworzyć "składnię przyjemną dla oka" i jak wzbogacać standardowe biblioteki .NET o własne reguły biznesowe. Zakończ projekt wyświetleniem sformatowanego raportu finansowego, w którym wszystkie ceny są wygenerowane przez Twoje nowe metody rozszerzające. Opisz w sprawozdaniu, dlaczego klasa rozszerzająca musi być statyczna.
Zrozumienie właściwości delegatów polegającej na możliwości przechowywania referencji do wielu metod jednocześnie (Multicast Delegate). Student uczy się budować systemy rozgłoszeniowe, w których jedno wywołanie uruchamia cały łańcuch zdarzeń.
Projektujesz system obsługi błędów dla krytycznego modułu bazy danych. W przypadku wystąpienia błędu krytycznego, aplikacja musi zareagować na kilka sposobów: zapisać log do pliku lokalnego, wyświetlić czerwony alert na konsoli oraz wysłać powiadomienie do administratora. Wykorzystaj mechanizm delegatów wielokrotnych (multicast), aby połączyć te trzy różne akcje (reprezentowane przez oddzielne metody) w jedną zmienną delegata. Twoim zadaniem jest pokazanie, jak za pomocą operatora "+=" można dynamicznie dokładać kolejne zadania do wykonania, a za pomocą "-=" łatwo je usuwać. Podczas symulacji awarii, program wywołuje tylko jedną instrukcję, która automatycznie uruchamia wszystkie podpięte funkcje ratunkowe. Pamiętaj, aby każda z metod wypisywała unikalny tekst, potwierdzający jej uruchomienie. To zadanie uczy, jak w elegancki sposób zarządzać złożonymi reakcjami systemu przy minimalnym nakładzie kodu w głównej logice sterującej. Zwróć uwagę, że kolejność wywołań wewnątrz delegata jest zgodna z kolejnością ich dodawania. Program powinien na koniec zademonstrować, co się stanie, gdy z listy wywołań zostanie usunięta jedna z metod. Jest to doskonałe przygotowanie do pracy z profesjonalnymi systemami typu Logging & Monitoring.