Student demonstruje umiejętność integracji mechanizmów delegatów, zdarzeń i wyrażeń lambda w jednym spójnym systemie. Zadanie ma na celu pokazanie praktycznego zastosowania współpracy tych trzech mechanizmów do budowy elastycznego silnika przetwarzania danych biznesowych. Student uczy się projektowania systemów, gdzie logika przetwarzania jest całkowicie odseparowana od mechanizmów transportu danych.
Budujesz centralny moduł przetwarzania zamówień dla platformy e-commerce, który musi być niezwykle elastyczny ze względu na ciągle zmieniające się wymagania biznesowe. Silnik ten przyjmuje zamówienie (obiekt klasy Zamowienie) i przekazuje je do dowolnej zarejestrowanej funkcji przetwarzającej. Kluczowym elementem jest zdarzenie OnZamowieniePrzyjete, które informuje moduły logistyczne, księgowe i marketingowe o nowym zamówieniu. Każdy z tych modułów sam decyduje, czy chce subskrybować zdarzenie i w jaki sposób na nie zareagować – wszystko to realizowane jest przez wyrażenia lambda. Twoim zadaniem jest stworzenie klasy SilnikZamowien, która posiada publiczne zdarzenie oraz metodę generyczną Procesuj, przyjmującą obiekt dowolnego typu i delegat do jego przetwarzania. Program w konsoli powinien symulować przyjęcie zamówienia, wywołanie zdarzenia i wyświetlenie reakcji wszystkich subskrybentów w różnych kolorach konsoli. To zadanie jest fundamentem pod budowę systemów typu Event-Driven Architecture, które są standardem w nowoczesnych aplikacjach rozproszonych.
using System; class Zamowienie { public int Id { get; set; } public string Klient { get; set; } public decimal Kwota { get; set; } public string Status { get; set; } } class SilnikZamowien { private static int licznikZamowien = 0; // Zdarzenie informujące o przyjęciu zamówienia public event Action<Zamowienie> OnZamowieniePrzyjete; public void Procesuj<T>(T dane, Action<T> processor) { // Najpierw wywołaj przekazany delegat przetwarzający processor(dane); // Jeśli to zamówienie, wywołaj zdarzenie if (dane is Zamowienie z) { licznikZamowien++; OnZamowieniePrzyjete?.Invoke(z); } } public static int LicznikZamowien => licznikZamowien; } class Program { static void Main() { var silnik = new SilnikZamowien(); // Subskrypcja zdarzenia przez wyrażenia lambda silnik.OnZamowieniePrzyjete += z => { Console.ForegroundColor = ConsoleColor.Green; Console.WriteLine($"[LOGISTYKA] Przygotowanie paczki dla: {z.Klient}"); }; silnik.OnZamowieniePrzyjete += z => { Console.ForegroundColor = ConsoleColor.Yellow; Console.WriteLine($"[KSIĘGOWOŚĆ] Fakturowanie zamówienia: {z.Id}, Kwota: {z.Kwota:c}"); }; silnik.OnZamowieniePrzyjete += z => { Console.ForegroundColor = ConsoleColor.Cyan; Console.WriteLine($"[MARKETING] Kampania powitalna dla nowego klienta: {z.Klient}"); }; // Utworzenie przykładowego zamówienia var zamowienie = new Zamowienie { Id = 1, Klient = "Jan Kowalski", Kwota = 299.99m, Status = "Nowe" }; Console.WriteLine("=== PRZYJĘCIE ZAMÓWIENIA ==="); // Przetwarzanie z delegatem prezentującym dane silnik.Procesuj(zamowienie, z => { Console.ForegroundColor = ConsoleColor.White; Console.WriteLine($"Przetwarzam zamówienie #{z.Id} klienta {z.Klient}"); }); Console.ForegroundColor = ConsoleColor.Gray; Console.WriteLine($"\nWszystkich zamówień: {SilnikZamowien.LicznikZamowien}"); } }
Zastosowanie delegata Comparison<T> wbudowanego w metodę Sort() kolekcji List do tworzenia niestandardowych kryteriów sortowania bez potrzeby tworzenia osobnych klas porównujących.
Pracujesz nad modułem analizy wyników sportowych dla aplikacji informacyjnej. Twoia baza danych zawiera listę zawodników (klasa Zawodnik z polami: Imie, Nazwisko, Punkty, Wiek). System musi umożliwiać sortowanie tej listy na wiele różnych sposobów: alfabetycznie według nazwiska, według liczby punktów (malejąco), według wieku (rosnąco) lub według kombinacji nazwiska i punktów. Zamiast tworzyć osobne klasy implementujące interfejs IComparer, użyjesz wyrażeń lambda bezpośrednio w wywołaniu metody List.Sort(Comparison<Zawodnik>). To podejście pozwala na zawarcie całej logiki sortowania w jednej linii kodu, co znacznie zwiększa czytelność i elastyczność rozwiązania. Program w konsoli powinien wyświetlić tabelę zawodników przed i po każdym sortowaniu, demonstrując różne kryteria uporządkowania. Finalnie wykaż, jak wiele wariantów sortowania można uzyskać bez dodatkowych klas, co jest doskonałym przykładem programowania deklaratywnego.
class Zawodnik { public string Imie { get; set; } public string Nazwisko { get; set; } public int Punkty { get; set; } public int Wiek { get; set; } }List<Zawodnik>.Sort(Comparison<Zawodnik> comparison) jest automatycznie dopasowany przez lambdę.(a, b) => a.Punkty.CompareTo(b.Punkty)(a, b) => b.Punkty.CompareTo(a.Punkty) – odwróć argumenty.return a.Punkty == b.Punkty ? a.Nazwisko.CompareTo(b.Nazwisko) : b.Punkty.CompareTo(a.Punkty);var posortowani = lista.OrderBy(z => z.Punkty).ToList();$"{z.Nazwisko,-15} {z.Imie,-10} {z.Punkty,6} {z.Wiek,5}"using System.Linq; w przypadku korzystania z metody OrderBy.Zrozumienie wzorca projektowego zdarzeń z niestandardowymi klasami EventArgs do przekazywania bogatych danych kontekstowych subskrybentom.
Projektujesz system monitoringu pogodowego dla aplikacji mobilnej, który powiadamia użytkowników o niebezpiecznych zjawiskach atmosferycznych. Stwórz klasę StacjaMeteo, która generuje zdarzenie OnAlertyPogodowe za każdym razem, gdy warunki atmosferyczne przekraczają bezpieczne progi. Zamiast używać prostego Action<string>, zdefiniujesz własną klasę ArgumentyAlertu dziedziczącą po EventArgs, która będzie przekazywać bogaty pakiet informacji: typ alertu, temperaturę, wilgotność, prędkość wiatru oraz listę dotkniętych regionów. Twoim zadaniem jest zarejestrowanie kilku subskrybentów (np. aplikacja mobilna, system SMS, panel informacyjny), z których każdy reaguje inaczej na ten sam alert w zależności od swoich potrzeb. Program symuluje odczyty czujników i generuje alerty, gdy wartości przekraczają zdefiniowane progi. To zadanie uczy, jak projektować rozbudowane systemy powiadomień, gdzie nadawca nie musi znać szczegółów implementacyjnych odbiorców. Finalny raport powinien zawierać chronologiczny log wszystkich wygenerowanych alertów z ich szczegółami.
class ArgumentyAlertu : EventArgs { public string TypAlertu { get; set; } public double Temperatura { get; set; } public List<string> Regiony { get; set; } }public event EventHandler<ArgumentyAlertu> OnAlertyPogodowe;OnAlertyPogodowe?.Invoke(this, new ArgumentyAlertu { TypAlertu = "MROZ", Temperatura = -15 });stacja.OnAlertyPogodowe += Stacja_OnAlerty;void Stacja_OnAlerty(object sender, ArgumentyAlertu e) { Console.WriteLine($"ALERT: {e.TypAlertu}"); }stacja.OnAlertyPogodowe += (s, e) => Console.WriteLine($"[SMS] Wysyłam alert do {e.Regiony.Count} regionów");stacja.OnAlertyPogodowe -= Stacja_OnAlerty;new Random().Next(-20, 40)Opanowanie delegata generycznego Func<T, TResult> do tworzenia uniwersalnych fabryk transformacji danych, które mogą być dynamicznie wybierane w czasie działania aplikacji.
Budujesz moduł transformacji danych dla systemu ETL (Extract-Transform-Load), który musi przetwarzać surowe dane z różnych źródeł i formatować je przed załadowaniem do hurtowni. Twoim zadaniem jest stworzenie klasy Transformator z metodą Transformuj, która przyjmuje wartość wejściową dowolnego typu oraz delegat Func<T, TResult> określający, jak przekształcić tę wartość. Zamiast pisać osobne metody dla każdego typu transformacji (np. WalidujCene, FormatujDate, KonwertujWalute), zdefiniujesz słownik Func<string, Func<string, string>>, gdzie kluczem jest nazwa transformacji, a wartością jest lambda realizująca to przekształcenie. Program w konsoli powinien prezentować menu transformacji, gdzie użytkownik wybiera operację, a system dynamicznie pobiera odpowiednią lambdę ze słownika i wykonuje ją na danych wejściowych. To zadanie uczy, jak budować elastyczne systemy konfigurowalne bez użycia instrukcji warunkowych if-else lub switch. Finalnie system powinien wyświetlać historię wszystkich zastosowanych transformacji.
using System.Linq; dla metody Reverse().Dictionary<string, Func<string, string>> operacje = new Dictionary<string, Func<string, string>>();operacje["trim"] = tekst => tekst.Trim();operacje["upper"] = tekst => tekst.ToUpper();operacje["slug"] = tekst => tekst.ToLower().Replace(" ", "-").Replace(",", "");operacje["rev"] = tekst => new string(tekst.Reverse().ToArray());Func<string, string> transform = operacje[klucz];string wynik = transform(daneWejsciowe);if (!operacje.ContainsKey(klucz))List<(string operacja, string wejscie, string wynik)> historia = new List<>();historia.Add((klucz, wejscie, wynik));Praktyczne zastosowanie delegata Action<T> do budowy systemu kolejkowania zadań z różnymi priorytetami, gdzie każde zadanie jest realizowane przez wyrażenie lambda.
Projektujesz harmonogram zadań dla systemu operacyjnego symulowanego w konsoli. Klasa Harmonogram posiada metodę DodajZadanie, która przyjmuje opis zadania (string), priorytet (int) oraz Action reprezentujący kod do wykonania. Twoim zadaniem jest zorganizowanie listy zadań według priorytetów – zadania o wyższym priorytecie (np. 10) wykonywane są przed zadaniami o niższym (np. 1), ponieważ wyższa wartość priorytetu oznacza pilniejsze zadanie. Program powinien najpierw sortować kolekcję zadań przed wykonaniem, a następnie uruchamiać każde zadanie sekwencyjnie, wyświetlając jego opis i komunikat o wykonaniu. Użytkownik w konsoli może dodawać nowe zadania z dowolną lambdą (np. zadanie obliczeniowe, zadanie formatujące tekst, zadanie logujące). To zadanie uczy, jak modelować rzeczywiste systemy operacyjne przy użyciu delegatów. Finalnie program powinien wyświetlić podsumowanie liczby wykonanych zadań oraz całkowity czas symulacji (mierzony w tickach procesora).
class Zadanie { public string Opis { get; set; } public int Priorytet { get; set; } public Action Akcja { get; set; } }harmonogram.Add(new Zadanie { Opis = "Oblicz sumę", Priorytet = 5, Akcja = () => Console.WriteLine("Obliczam...") });harmonogram.Sort((a, b) => b.Priorytet.CompareTo(a.Priorytet));foreach (var z in harmonogram) { Console.WriteLine($"Wykonuję: {z.Opis}"); z.Akcja(); }DateTime.Now.ToString("HH:mm:ss.fff")(string tekst) => Console.WriteLine(tekst) – ale Action nie ma parametrów, więc używaj domknięć (closures).int i = 0;harmonogram.Add(new Zadanie { Akcja = () => Console.WriteLine($"Zadanie numer {i}") });int wykonanych = 0; foreach (...) { z.Akcja(); wykonanych++; }Tworzenie metod rozszerzających dla wbudowanych typów języka C# w celu dodania niestandardowej logiki biznesowej w sposób elegancki i czytelny.
Pracujesz nad modułem analizy treści dla systemu CMS (Content Management System). Zamiast tworzyć klasy narzędziowe z metodami statycznymi, musisz napisać statyczną klasę StringAnalyzer zawierającą metody rozszerzające dla typu string. Każda z tych metod będzie wywoływana bezpośrednio na obiekcie string, co sprawia, że kod przypomina naturalny język angielski. Wymagane metody to: ZliczSamogloski() – zwracająca liczbę samogłosek w tekście, CzyPalindrom() – sprawdzająca, czy tekst czyta się tak samo od przodu i od tyłu, PodzielNaZdania() – zwracająca tablicę stringów z poszczególnymi zdaniami, Skroc(int maxDlugosc, string suffix) – skracająca tekst do podanej długości z dodanym sufiksem (np. "..."). Twoim zadaniem jest zademonstrowanie tych metod w Main na kilku przykładowych tekstach wprowadzonych przez użytkownika. Program powinien prezentować statystyki tekstu w estetyczny sposób. To zadanie uczy, jak pisać kod, który jest nie tylko funkcjonalny, ale również sam dokumentuje swoje przeznaczenie poprzez intuicyjną składnię.
this przed pierwszym parametrem każdej metody.public static class StringAnalyzer – musi być statyczna.public static int ZliczSamogloski(this string s) – słowo kluczowe this przed pierwszym parametrem.string samogloski = "aeiouyąęóAEIOUYĄĘÓ"; foreach(char c in s) if(samogloski.Contains(c)) count++;string clean = s.ToLower().Replace(" ", ""); return clean == new string(clean.Reverse().ToArray());return s.Split(new[] { '.', '!', '?' }, StringSplitOptions.RemoveEmptyEntries);return s.Length > max ? s.Substring(0, max) + suffix : s;string tekst = "Hello World"; int ile = tekst.ZliczSamogloski();"ALA MA KOTA".ToLower().Skroc(10, "...")using System.Linq; dla metody Reverse().using NazwaPrzestrzeni;Zastosowanie metod rozszerzających do dodawania funkcjonalności do typów numerycznych, tworząc eleganckie API do obliczeń naukowych i finansowych.
Budujesz osobistą bibliotekę narzędziową (tzw. utility library) dla działu analiz finansowych. Zamiast tworzyć klasę Matematyka z metodami statycznymi, postanawiasz dodać metody rozszerzające bezpośrednio do typów int, double i decimal. Wymagane rozszerzenia to: DoPotegi(int podstawa, int wykladnik) – obliczająca potęgę danej liczby, JestParzysta() – zwracająca informację o parzystości, Silnia() – obliczająca silnię (dla int), DoFormatuWalutowego(string symbol) – zwracająca sformatowaną wartość jako string walutowy (dla decimal), CzyWiekszeOd(double prog) – sprawdzająca czy wartość przekracza próg (dla double). Twoim zadaniem jest stworzenie statycznej klasy MathExtensions z tymi metodami rozszerzającymi i zademonstrowanie ich w programie konsolowym. Każde wywołanie powinno wyglądać naturalnie, jakby metoda była wbudowana w typ: np. "125.Silnia()" lub "99.5.CzyWiekszeOd(100)". Program powinien prezentować obliczenia finansowe i naukowe, demonstrując czytelność kodu opartego na rozszerzeniach.
public static long Silnia(this int n)long wynik = 1; for (int i = 2; i <= n; i++) wynik *= i; return wynik;public static bool CzyWiekszeOd(this double val, double prog) => val > prog;public static string DoFormatuWalutowego(this decimal val, string symbol) => $"{symbol} {val:N2}";public static int DoPotegi(this int podstawa, int wykladnik) { int wynik = 1; for (int i = 0; i < wykladnik; i++) wynik *= podstawa; return wynik; }Console.WriteLine(5.Silnia());int[] liczby = { 1, 2, 3, 4, 5 }; var srednia = liczby.Average(); Console.WriteLine(srednia.CzyWiekszeOd(3) ? "Powyżej progu" : "Poniżej");int x = 10; bool parzysta = x.JestParzysta();using System.Linq; dla Average()).