Wymagania formalne:
Sprawozdanie z szóstego modułu powinno skupiać się na elegancji i zwięzłości kodu wynikającej z użycia wyrażeń lambda oraz metod rozszerzających. W rozwiązaniach należy wykazać poprawne użycie zdarzeń z obsługą subskrypcji i odsubskrybowania. Wymagana jest strona tytułowa, treść zadania, kody źródłowe z komentarzami, zrzuty ekranu oraz sekcja wniosków dotycząca zalet programowania funkcyjnego w C#.
D6.1
Kalkulator Wynagrodzeń (Delegaty finansowe)
Cel

Zastosowanie własnych delegatów do obliczania różnych wariantów finansowych wynagrodzenia (Netto/Brutto). Student uczy się przekazywać konkretne funkcje przeliczające jako parametry do uniwersalnego procesora list płac.

Scenariusz

Budujesz system kadrowo-płacowy dla dużej korporacji, która zatrudnia pracowników na różnych typach umów: o pracę, zlecenie oraz B2B. Każdy typ umowy ma inny algorytm wyliczania kwoty "na rękę" na podstawie kwoty bazowej. Twoim zadaniem jest zdefiniowanie delegata o nazwie "KalkulatorPensji", który przyjmuje kwotę (decimal) i zwraca wynik po potrąceniu podatków. Musisz napisać trzy niezależne metody realizujące te obliczenia dla każdego typu umowy. Następnie zaprojektuj klasę "ProcesorPlac", która posiada metodę WygenerujRaport, przyjmującą kwotę oraz wspomniany delegat. Dzięki takiemu podejściu, Twój procesor nie musi znać skomplikowanych przepisów podatkowych – on po prostu wywołuje przekazaną mu funkcję. Program w konsoli powinien symulować wystawienie trzech pasków płacowych dla tej samej kwoty brutto, pokazując ogromną elastyczność delegatów w biznesie. To zadanie uczy, jak projektować systemy obliczeniowe, które są łatwe do rozbudowy o nowe przepisy prawne bez modyfikacji głównego silnika raportowego. Program kończy pracę wyświetleniem sumy wypłat dla wszystkich przetestowanych wariantów. Precyzja obliczeń finansowych i czytelność kodu są tutaj kluczowe.

Sugerowane kroki do wykonania
  1. Zdefiniuj delegat: public delegate decimal SalaryCalculator(decimal baseSum).
  2. Napisz metodę do obliczania pensji UoP (np. -19% podatku i koszty).
  3. Napisz metodę do obliczania pensji B2B (np. stały ryczałt).
  4. Napisz metodę dla Umowy Zlecenie (uproszczony podatek).
  5. Zaprojektuj metodę Procesuj(decimal kwota, SalaryCalculator calc).
  6. Wewnątrz metody wywołaj przekazany delegat i wyświetl sformatowany wynik.
  7. W metodzie Main zadeklaruj testową kwotę 10 000 PLN.
  8. Wywołaj Procesuj trzykrotnie, za każdym razem podając inną metodę obliczeniową.
  9. Zademonstruj użycie wyrażenia lambda do stworzenia "na poczekaniu" czwartego wariantu (np. premia 10%).
  10. Wyjaśnij w komentarzu, dlaczego delegat pozwala na zachowanie zasady Open/Closed (otwarte na rozszerzenia, zamknięte na modyfikacje).
D6.2
Własny Mechanizm Przycisku (Symulacja GUI i Events)
Cel

Zrozumienie jak działają zdarzenia w interfejsach graficznych poprzez ich ręczną symulację w konsoli. Student uczy się projektować interaktywne komponenty, które powiadamiają inne części systemu o interakcji użytkownika.

Scenariusz

Choć pracujesz w konsoli, Twoim zadaniem jest stworzenie klasy "Przycisk", która zachowuje się jak prawdziwy element okna Windows. Klasa ta powinna posiadać publiczne zdarzenie "OnClick". Każdy, kto chce zareagować na kliknięcie (np. klasa "Lampka", która ma się zapalić, lub klasa "Loger", która ma zapisać fakt kliknięcia), musi zasubskrybować to zdarzenie. Twoim wyzwaniem jest zaimplementowanie metody "Kliknij()", która po wywołaniu w konsoli uruchomi wszystkie podpięte funkcje. Aby zadanie było bardziej profesjonalne, użyj standardowego delegata .NET: EventHandler lub Action. Stwórz w Main obiekt przycisku, podepnij do niego dwie różne akcje (wyrażenia lambda) i wywołaj metodę Kliknij() kilka razy. Następnie odepnij jedną z akcji i pokaż, że system nadal działa poprawnie, wykonując tylko pozostałe zadania. Takie podejście uczy projektowania oprogramowania sterowanego zdarzeniami (Event-Driven Programming), co jest fundamentem tworzenia aplikacji mobilnych i desktopowych. Program kończy pracę krótkim podziękowaniem od wszystkich "aktywnych" subskrybentów przycisku. Wykorzystaj kolory konsoli, aby odróżnić reakcje różnych modułów aplikacji.

Sugerowane kroki do wykonania
  1. Stwórz klasę Przycisk z właściwością Nazwa.
  2. Zdefiniuj w niej zdarzenie: public event Action OnClick.
  3. Zaimplementuj metodę public void Push(), która wywołuje OnClick?.Invoke().
  4. W Main utwórz instancję przycisku o nazwie "ZATWIERDŹ".
  5. Zasubskrybuj zdarzenie przez lambdę wypisującą: "Zapisano dane w bazie".
  6. Zasubskrybuj zdarzenie przez drugą lambdę: "Wysłano e-mail potwierdzający".
  7. Wywołaj przycisk.Push() i zaobserwuj obie reakcje.
  8. Użyj operatora -=, aby usunąć subskrypcję e-maila.
  9. Wywołaj Push() ponownie i sprawdź, czy tym razem e-mail nie został wysłany.
  10. Zapisz wniosek: dlaczego zdarzenia są bezpieczniejsze od publicznych pól delegatów?
D6.3
Inteligentny Filtr Produktów (Lambda i Predicate)
Cel

Zaawansowane filtrowanie kolekcji z wykorzystaniem wyrażeń lambda jako dynamicznych warunków logicznych. Student uczy się pisać generyczne silniki wyszukiwania, które nie wymagają zmiany kodu przy wprowadzaniu nowych kryteriów biznesowych.

Scenariusz

Pracujesz nad wyszukiwarką dla sklepu internetowego z elektroniką. Twoja baza zawiera listę obiektów klasy Produkt (pola: Nazwa, Cena, Kategoria, CzyDostepny). Musisz napisać jedną, uniwersalną metodę o nazwie "FiltrujProdukty", która przyjmuje listę oraz delegat Predicate<Produkt>. Twoim zadaniem jest przeprowadzenie serii testów, w których będziesz szukać: produktów z kategorii 'Laptopy' tańszych niż 3000 zł, wszystkich niedostępnych produktów oraz tych, których nazwa zawiera słowo 'Pro'. Każde z tych zapytań musi być zrealizowane jako pojedyncze wyrażenie lambda przekazane do wspomnianej metody. Program powinien wyświetlić wyniki każdego filtrowania w estetyczny sposób, podając liczbę znalezionych pozycji. To zadanie uczy, jak ogromną moc daje łączenie lambd z kolekcjami generycznymi, co jest podstawą pracy z dużymi zbiorami danych. Zwróć uwagę na zwięzłość zapisu – cała logika filtru powinna zmieścić się w jednej linii wywołania. Finalnie podsumuj, jak dużą oszczędność linii kodu daje takie podejście w porównaniu do tradycyjnych pętli. Jest to idealny przykład przygotowujący do nauki zaawansowanego LINQ.

Sugerowane kroki do wykonania
  1. Zdefiniuj klasę Produkt z 4 właściwościami.
  2. Wypełnij listę List<Produkt> ośmioma zróżnicowanymi przedmiotami.
  3. Napisz metodę pomocniczą Filtruj(List<Produkt> lista, Predicate<Produkt> warunek).
  4. W Main wywołaj Filtruj z lambdą szukającą produktów z konkretnej kategorii.
  5. Wywołaj Filtruj szukając produktów w przedziale cenowym (cena >= x && cena <= y).
  6. Wywołaj Filtruj dla produktów niedostępnych (CzyDostepny == false).
  7. Wypisz wyniki każdego filtrowania z odpowiednim nagłówkiem w konsoli.
  8. Przetestuj łączenie warunków (np. kategoria X i cena Y) w jednej lambdzie.
  9. Użyj List.FindAll() – wbudowanej metody .NET – i porównaj ją ze swoją metodą Filtruj.
  10. Zapisz refleksję o czytelności kodu w sprawozdaniu.
D6.4
Automatyczny Loger Zdarzeń (Subskrypcja Systemowa)
Cel

Integracja klasy generującej zdarzenia z klasą rejestrującą je w pamięci. Student uczy się projektować systemy "obserwatorów", gdzie jeden obiekt automatycznie śledzi i dokumentuje aktywność drugiego za pomocą bezpiecznych zdarzeń.

Scenariusz

Zaprojektuj system monitorowania temperatury w serwerowni. Stwórz klasę "Termometr", która co sekundę (lub w pętli) generuje losową temperaturę i jeśli przekroczy ona 40 stopni, wywołuje zdarzenie "OnOverheat". Równolegle stwórz klasę "Rejestrator", która posiada prywatną listę stringów o nazwie "Logi". Zadaniem Rejestratora jest subskrybowanie zdarzenia z Termometru i za każdym razem, gdy nastąpi przegrzanie, dopisanie do swojej listy notatki: "ALARM: Temperatura wyniosła X stopni o godzinie Y". W Main uruchom symulację pracy serwerowni przez 10 cykli, a na koniec poproś klasę Rejestrator o wyświetlenie wszystkich zebranych logów. To zadanie uczy, jak obiekty mogą ze sobą współpracować bez bezpośredniego wywoływania swoich metod – Termometr tylko "krzyczy", że jest gorąco, a Rejestrator "słucha" i notuje. Jest to modelowy przykład separacji obowiązków (Separation of Concerns). Upewnij się, że Rejestrator poprawnie odpina się od Termometru na koniec programu, co jest dobrą praktyką zarządzania pamięcią w .NET. Finalny raport powinien zawierać chronologiczne zestawienie wszystkich zarejestrowanych incydentów temperatury.

Sugerowane kroki do wykonania
  1. Stwórz klasę Termometr ze zdarzeniem public event Action<double> OnOverheat.
  2. Stwórz klasę Rejestrator posiadającą List<string> historia.
  3. W klasie Rejestrator dodaj metodę ZapiszLog(double temp) pasującą do delegata Action.
  4. W Main utwórz obiekty obu klas.
  5. Dokonaj subskrypcji: termometr.OnOverheat += rejestrator.ZapiszLog.
  6. Uruchom pętlę for (10 iteracji) generującą losowe temperatury w termometrze.
  7. Wypisz na konsolę każdy odczyt temperatury (również te bezpieczne).
  8. Po zakończeniu pętli wywołaj metodę z Rejestratora wypisującą wszystkie zgromadzone logi błędów.
  9. Odsubskrybuj zdarzenie i wykonaj jeszcze jedną próbę (log nie powinien się dopisać).
  10. Opisz w sprawozdaniu rolę mechanizmu zdarzeń w architekturze typu "Observer".
D6.5
Menedżer Tekstu: Własne Metody Rozszerzające
Cel

Opanowanie techniki rozszerzania wbudowanych typów języka C# o niestandardową logikę biznesową. Student uczy się tworzyć biblioteki pomocnicze, które są zintegrowane bezpośrednio z obiektami, na których operują.

Scenariusz

Pracujesz nad systemem do analizy treści artykułów prasowych. Zamiast tworzyć skomplikowane klasy narzędziowe, musisz napisać trzy metody rozszerzające dla typu string, które będą dostępne dla każdego tekstu w Twojej aplikacji. Pierwsza metoda "LiczSlowa()" powinna zwracać liczbę słów w tekście. Druga metoda "CzyZdanie()" powinna sprawdzać, czy tekst kończy się kropką, pytajnikiem lub wykrzyknikiem. Trzecia metoda "Skroc(int max)" powinna skracać zbyt długi tekst i dodawać na końcu wielokropek. Twoim zadaniem jest zademonstrowanie użycia tych metod w konsoli na kilku przykładowych zdaniach, wywołując je "po kropce" prosto z obiektów typu string (np. "Cześć świat".LiczSlowa()). To zadanie uczy, jak pisać niezwykle czytelny kod, który niemal przypomina naturalny język angielski. Dzięki metodom rozszerzającym Twoja biblioteka do obróbki tekstu staje się niezwykle intuicyjna dla innych deweloperów. Pamiętaj, że wszystkie metody rozszerzające muszą znajdować się w statycznej klasie i posiadać słowo kluczowe "this". Zakończ zadanie wyświetleniem pełnej analizy statystycznej dla dłuższego akapitu tekstu wprowadzonego przez użytkownika.

Sugerowane kroki do wykonania
  1. Utwórz statyczną klasę o nazwie StringExtensions.
  2. Zdefiniuj metodę public static int WordCount(this string s).
  3. Implementacja: użyj s.Split z odpowiednimi separatorami, aby policzyć wyrazy.
  4. Zdefiniuj metodę public static bool IsSentence(this string s).
  5. Zdefiniuj metodę public static string Shorten(this string s, int maxChars).
  6. W Main pobierz od użytkownika dowolne zdanie.
  7. Wypisz wynik działania metody WordCount() wywołanej bezpośrednio na zmiennej wejściowej.
  8. Sprawdź i wyświetl czy zdanie jest poprawne gramatycznie (metoda IsSentence).
  9. Zasymuluj wyświetlanie podglądu wiadomości za pomocą metody Shorten(10).
  10. Wyjaśnij wniosek: dlaczego metody rozszerzające wymagają statycznej klasy i statycznej metody?