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 wyrejestrowania (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#.
01
Zadanie wprowadzające: Uniwersalny Silnik Przetwarzania Danych
Cel

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.

Scenariusz

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.

Sugerowane kroki do wykonania
  1. Zdefiniuj klasę Zamowienie z polami: Id, Klient, Kwota, Status.
  2. Stwórz klasę SilnikZamowien z publicznym zdarzeniem Action<Zamowienie> OnZamowieniePrzyjete.
  3. Zaimplementuj metodę generyczną void Procesuj(T dane, Action<T> processor).
  4. W metodzie Procesuj najpierw wywołaj przekazany delegat processor, potem zdarzenie (jeśli T to Zamowienie).
  5. W Main utwórz obiekt SilnikZamowien i zasubskrybuj zdarzenie trzema lambdami: logistyk, księgowy, marketing.
  6. Dodaj do każdej lambdy prefiks z kolorem konsoli (np. Console.ForegroundColor).
  7. Utwórz przykładowe zamówienie i wywołaj SilnikZamowien.Procesuj z lambdą prezentującą dane.
  8. Zademonstruj odsubskrypcję jednego z modułów i sprawdź, czy pozostałe nadal działają.
  9. Wyświetl podsumowanie liczby obsłużonych zamówień.
  10. Zapisz w komentarzu refleksję o zaletach separacji logiki przez delegaty.
Kod rozwiązania
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}");
    }
}
                    
6.1
Sortownik List z Wyrażeniami Lambda (Comparison Delegate)
Cel

Zastosowanie delegata Comparison<T> wbudowanego w metodę Sort() kolekcji List do tworzenia niestandardowych kryteriów sortowania bez potrzeby tworzenia osobnych klas porównujących.

Scenariusz

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.

Sugerowane kroki do wykonania
  1. Zdefiniuj klasę Zawodnik z właściwościami: Imie, Nazwisko, Punkty, Wiek.
  2. Utwórz List<Zawodnik> i wypełnij ją 6 przykładowymi rekordami o zróżnicowanych wartościach.
  3. Wyświetl listę w formie tabeli przed jakimkolwiek sortowaniem.
  4. Wywołaj lista.Sort((a, b) => a.Nazwisko.CompareTo(b.Nazwisko)) – sortowanie alfabetyczne.
  5. Wywołaj lista.Sort((a, b) => b.Punkty.CompareTo(a.Punkty)) – sortowanie punktów malejąco.
  6. Wywołaj lista.Sort((a, b) => a.Wiek.CompareTo(b.Wiek)) – sortowanie według wieku.
  7. Zaimplementuj sortowanie kombinowane: najpierw punkty, potem nazwisko (zwróć a.Punkty == b.Punkty ? a.Nazwisko.CompareTo(b.Nazwisko) : b.Punkty.CompareTo(a.Punkty)).
  8. Wyświetl wyniki po każdym sortowaniu z odpowiednim nagłówkiem.
  9. Użyj metody OrderBy z LINQ jako alternatywy nie modyfikującej oryginalnej listy.
  10. Zapisz w sprawozdaniu wniosek o oszczędności kodu dzięki lambdom w sortowaniu.
Wskazówki wykonania
  • Deklaracja klasy: class Zawodnik { public string Imie { get; set; } public string Nazwisko { get; set; } public int Punkty { get; set; } public int Wiek { get; set; } }
  • Delegat Comparison: List<Zawodnik>.Sort(Comparison<Zawodnik> comparison) jest automatycznie dopasowany przez lambdę.
  • Sortowanie rosnące: (a, b) => a.Punkty.CompareTo(b.Punkty)
  • Sortowanie malejące: (a, b) => b.Punkty.CompareTo(a.Punkty) – odwróć argumenty.
  • Sortowanie złożone: użyj instrukcji warunkowej w lambdzie, np. return a.Punkty == b.Punkty ? a.Nazwisko.CompareTo(b.Nazwisko) : b.Punkty.CompareTo(a.Punkty);
  • LINQ OrderBy nie modyfikuje oryginalnej listy: var posortowani = lista.OrderBy(z => z.Punkty).ToList();
  • Wyświetlanie tabeli: $"{z.Nazwisko,-15} {z.Imie,-10} {z.Punkty,6} {z.Wiek,5}"
  • Metoda CompareTo zwraca int: ujemny gdy a < b, zero gdy równe, dodatni gdy a > b.
  • Pamiętaj o dodaniu using System.Linq; w przypadku korzystania z metody OrderBy.
Ilustracja do zadania 6-1
6.2
System Alertów Pogodowych (Custom EventArgs)
Cel

Zrozumienie wzorca projektowego zdarzeń z niestandardowymi klasami EventArgs do przekazywania bogatych danych kontekstowych subskrybentom.

Scenariusz

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.

Sugerowane kroki do wykonania
  1. Zdefiniuj klasę ArgumentyAlertu : EventArgs z polami: TypAlertu, Temperatura, Wilgotnosc, PredkoscWiatru, Regiony (List<string>).
  2. Stwórz klasę StacjaMeteo z delegatem public event EventHandler<ArgumentyAlertu> OnAlertyPogodowe.
  3. Zaimplementuj metodę SymulujOdczyt() generującą losowe warunki i sprawdzającą progi.
  4. Jeśli warunek przekracza próg, utwórz ArgumentyAlertu i wywołaj zdarzenie z tymi argumentami.
  5. W Main utwórz instancję stacji i zasubskrybuj zdarzenie trzema handlerami (aplikacja, SMS, panel).
  6. Każdy handler powinien wyświetlać informacje w innym formacie lub z innym priorytetem.
  7. Użyj pętli symulującej 5 odczytów czujników w odstępach.
  8. Zapisz logi wszystkich alertów do listy i wyświetl na końcu programu.
  9. Odsubskrybuj jeden handler i wykonaj kolejną symulację, demonstrując selektywność.
  10. Wyjaśnij w sprawozdaniu, dlaczego EventArgs jest lepszy od prostego stringa.
Wskazówki wykonania
  • Definicja EventArgs: class ArgumentyAlertu : EventArgs { public string TypAlertu { get; set; } public double Temperatura { get; set; } public List<string> Regiony { get; set; } }
  • Delegat zdarzenia: public event EventHandler<ArgumentyAlertu> OnAlertyPogodowe;
  • Wywołanie zdarzenia: OnAlertyPogodowe?.Invoke(this, new ArgumentyAlertu { TypAlertu = "MROZ", Temperatura = -15 });
  • Subskrypcja z metodą: stacja.OnAlertyPogodowe += Stacja_OnAlerty;
  • Metoda handler: void Stacja_OnAlerty(object sender, ArgumentyAlertu e) { Console.WriteLine($"ALERT: {e.TypAlertu}"); }
  • Subskrypcja z lambdą: stacja.OnAlertyPogodowe += (s, e) => Console.WriteLine($"[SMS] Wysyłam alert do {e.Regiony.Count} regionów");
  • Odsubskrypcja: stacja.OnAlertyPogodowe -= Stacja_OnAlerty;
  • Losowa wartość: new Random().Next(-20, 40)
  • Próg alertu: np. temperatura < -10 lub > 35 stopni.
Ilustracja do zadania 6-2
6.3
Fabryka Transformacji Danych (Func Delegate)
Cel

Opanowanie delegata generycznego Func<T, TResult> do tworzenia uniwersalnych fabryk transformacji danych, które mogą być dynamicznie wybierane w czasie działania aplikacji.

Scenariusz

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.

Sugerowane kroki do wykonania
  1. Utwórz klasę Transformator z metodą string Transformuj(string wejscie, Func<string, string> operacja).
  2. Zdefiniuj Dictionary<string, Func<string, string>> o nazwie operacje zawierający minimum 5 transformacji (Trim, Upper, Lower, Slugify, Reversed).
  3. Zaimplementuj transformację Slugify jako: tekst.ToLower().Replace(" ", "-").
  4. Zaimplementuj transformację Reversed jako: new string(tekst.Reverse().ToArray()).
  5. W Main wyświetl menu dostępnych operacji pobierając klucze ze słownika.
  6. Poproś użytkownika o wprowadzenie tekstu i wybór operacji.
  7. Pobierz lambdę ze słownika: operacje[wybranaOperacja] i wywołaj Transformuj.
  8. Zapisz historię transformacji do listy i wyświetl ją na końcu programu.
  9. Dodaj walidację – jeśli wybrana operacja nie istnieje, wyświetl komunikat błędu.
  10. Pamiętaj o dodaniu using System.Linq; dla metody Reverse().
  11. Wyjaśnij w sprawozdaniu, jak słownik delegatów eliminunie potrzebę instrukcji switch.
Wskazówki wykonania
  • Deklaracja słownika: Dictionary<string, Func<string, string>> operacje = new Dictionary<string, Func<string, string>>();
  • Dodanie transformacji: operacje["trim"] = tekst => tekst.Trim();
  • Dodanie transformacji: operacje["upper"] = tekst => tekst.ToUpper();
  • Slugify: operacje["slug"] = tekst => tekst.ToLower().Replace(" ", "-").Replace(",", "");
  • Reversed: operacje["rev"] = tekst => new string(tekst.Reverse().ToArray());
  • Pobranie lambdy: Func<string, string> transform = operacje[klucz];
  • Wywołanie: string wynik = transform(daneWejsciowe);
  • Walidacja: if (!operacje.ContainsKey(klucz))
  • Lista historii: List<(string operacja, string wejscie, string wynik)> historia = new List<>();
  • Historia krotek: historia.Add((klucz, wejscie, wynik));
Ilustracja do zadania 6-3
6.4
Kolejka Priorytetowa Zadań (Action Delegate)
Cel

Praktyczne zastosowanie delegata Action<T> do budowy systemu kolejkowania zadań z różnymi priorytetami, gdzie każde zadanie jest realizowane przez wyrażenie lambda.

Scenariusz

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).

Sugerowane kroki do wykonania
  1. Zdefiniuj klasę Zadanie z polami: Opis, Priorytet (int), Akcja (Action).
  2. Utwórz List<Zadanie> harmonogram i metodę DodajZadanie(string opis, int priorytet, Action akcja).
  3. Zaimplementuj metodę WykonajWszystko(), która sortuje listę po priorytecie (malejąco) i wykonuje każde zadanie.
  4. W Main dodaj 5 zadań testowych z różnymi priorytetami, każde z inną lambdą:
    • Zadanie 1: Symulacja obliczeń matematycznych (np. suma liczb)
    • Zadanie 2: Formatowanie tekstu (np. wypisanie powitania)
    • Zadanie 3: Operacja na pliku (symulacja Console.WriteLine)
    • Zadanie 4: Wysłanie powiadomienia
    • Zadanie 5: Cleanup (czyszczenie zasobów)
  5. Przed wykonaniem wyświetl listę zadań posortowaną według priorytetu.
  6. Wywołaj WykonajWszystko() i obserwuj kolejność realizacji.
  7. Dodaj timestamp do każdego logu wykonania zadania.
  8. Zlicz i wyświetl całkowitą liczbę wykonanych zadań.
  9. Wyjaśnij w sprawozdaniu, dlaczego delegaty Action idealnie pasują do wzorca Command.
Wskazówki wykonania
  • Deklaracja klasy Zadanie: class Zadanie { public string Opis { get; set; } public int Priorytet { get; set; } public Action Akcja { get; set; } }
  • Dodawanie zadania: harmonogram.Add(new Zadanie { Opis = "Oblicz sumę", Priorytet = 5, Akcja = () => Console.WriteLine("Obliczam...") });
  • Sortowanie: harmonogram.Sort((a, b) => b.Priorytet.CompareTo(a.Priorytet));
  • Wykonanie: foreach (var z in harmonogram) { Console.WriteLine($"Wykonuję: {z.Opis}"); z.Akcja(); }
  • Timestamp: DateTime.Now.ToString("HH:mm:ss.fff")
  • Lambda z parametrem: (string tekst) => Console.WriteLine(tekst) – ale Action nie ma parametrów, więc używaj domknięć (closures).
  • Zmienna w domknięciu: int i = 0;harmonogram.Add(new Zadanie { Akcja = () => Console.WriteLine($"Zadanie numer {i}") });
  • Licznik wykonanych: int wykonanych = 0; foreach (...) { z.Akcja(); wykonanych++; }
  • Przetestuj z zadaniem o najwyższym i najniższym priorytecie – sprawdź kolejność wykonania.
Ilustracja do zadania 6-4
6.5
Analizator Tekstu: Rozszerzenia dla String (Extension Methods)
Cel

Tworzenie metod rozszerzających dla wbudowanych typów języka C# w celu dodania niestandardowej logiki biznesowej w sposób elegancki i czytelny.

Scenariusz

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ę.

Sugerowane kroki do wykonania
  1. Utwórz statyczną klasę StringAnalyzer z modyfikatorem this przed pierwszym parametrem każdej metody.
  2. Zdefiniuj metodę public static int ZliczSamogloski(this string tekst) – użyj pętli i warunku sprawdzającego polskie samogłoski.
  3. Zdefiniuj metodę public static bool CzyPalindrom(this string tekst) – porównaj tekst z jego odwróconą wersją (używając ToLower() i usuwając spacje).
  4. Zdefiniuj metodę public static string[] PodzielNaZdania(this string tekst) – użyj metody Split z separatorami '.', '!', '?'.
  5. Zdefiniuj metodę public static string Skroc(this string tekst, int max, string suffix) – zwróć fragment tekstu z sufiksem.
  6. W Main pobierz od użytkownika dowolne zdanie lub akapit tekstu.
  7. Wywołaj wszystkie metody rozszerzające bezpośrednio na zmiennej string: tekst.ZliczSamogloski().
  8. Wyświetl wyniki każdej analizy z opisem słownym.
  9. Zaimplementuj łańcuchowe wywołania: np. tekst.ToLower().Skroc(20, "...").
  10. Wyjaśnij w sprawozdaniu, dlaczego metody rozszerzające wymagają statycznej klasy i słowa kluczowego this.
Wskazówki wykonania
  • Definicja klasy rozszerzającej: public static class StringAnalyzer – musi być statyczna.
  • Metoda rozszerzająca: public static int ZliczSamogloski(this string s) – słowo kluczowe this przed pierwszym parametrem.
  • Zliczanie samogłosek: string samogloski = "aeiouyąęóAEIOUYĄĘÓ"; foreach(char c in s) if(samogloski.Contains(c)) count++;
  • Palindrom: string clean = s.ToLower().Replace(" ", ""); return clean == new string(clean.Reverse().ToArray());
  • Podział na zdania: return s.Split(new[] { '.', '!', '?' }, StringSplitOptions.RemoveEmptyEntries);
  • Skracanie: return s.Length > max ? s.Substring(0, max) + suffix : s;
  • Wywołanie: string tekst = "Hello World"; int ile = tekst.ZliczSamogloski();
  • Łączenie: "ALA MA KOTA".ToLower().Skroc(10, "...")
  • Metody rozszerzające NIE mogą nadpisywać metod instancyjnych – mają niższy priorytet.
  • Pamiętaj o dodaniu using System.Linq; dla metody Reverse().
  • Przestrzeń nazw musi być zaimportowana, jeśli klasa rozszerzająca jest w innym pliku: using NazwaPrzestrzeni;
Ilustracja do zadania 6-5
6.6
Biblioteka Matematyczna: Rozszerzenia dla Liczb (Extension Methods)
Cel

Zastosowanie metod rozszerzających do dodawania funkcjonalności do typów numerycznych, tworząc eleganckie API do obliczeń naukowych i finansowych.

Scenariusz

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.

Sugerowane kroki do wykonania
  1. Utwórz statyczną klasę MathExtensions z rozszerzeniami dla int.
  2. Zdefiniuj public static long Silnia(this int n) – użyj pętli for lub rekurencji.
  3. Zdefiniuj public static bool JestParzysta(this int n) – zwróć n % 2 == 0.
  4. Zdefiniuj public static int DoPotegi(this int podstawa, int wykladnik) – oblicz potęgę.
  5. Dodaj rozszerzenie dla double: public static bool CzyWiekszeOd(this double val, double prog).
  6. Dodaj rozszerzenie dla decimal: public static string DoFormatuWalutowego(this decimal val, string symbol).
  7. W Main wywołaj wszystkie rozszerzenia na odpowiednich typach literalnych.
  8. Zademonstruj: 5.Silnia(), 10.JestParzysta(), 2.DoPotegi(8), 150.5.CzyWiekszeOd(150), 2999.99m.DoFormatuWalutowego("PLN").
  9. Stwórz bardziej złożony przykład: oblicz średnią wartość i sprawdź czy jest większa od progu.
  10. Wyjaśnij w sprawozdaniu, jak rozszerzenia zmieniają sposób myślenia o projektowaniu API.
Wskazówki wykonania
  • Rozszerzenie dla int: public static long Silnia(this int n)
  • Silnia w pętli: long wynik = 1; for (int i = 2; i <= n; i++) wynik *= i; return wynik;
  • Rozszerzenie dla double: public static bool CzyWiekszeOd(this double val, double prog) => val > prog;
  • Rozszerzenie dla decimal: public static string DoFormatuWalutowego(this decimal val, string symbol) => $"{symbol} {val:N2}";
  • Potęga: public static int DoPotegi(this int podstawa, int wykladnik) { int wynik = 1; for (int i = 0; i < wykladnik; i++) wynik *= podstawa; return wynik; }
  • Wywołanie na literale: Console.WriteLine(5.Silnia());
  • Przykład łańcuchowy: int[] liczby = { 1, 2, 3, 4, 5 }; var srednia = liczby.Average(); Console.WriteLine(srednia.CzyWiekszeOd(3) ? "Powyżej progu" : "Poniżej");
  • Rozszerzenia działają też na zmiennych: int x = 10; bool parzysta = x.JestParzysta();
  • Przestrzeń nazw: upewnij się, że plik z rozszerzeniami jest w tej samej przestrzeni lub użyj using (np. using System.Linq; dla Average()).
  • Silnia dla 0 zwraca 1 – obsłuż ten przypadek brzegowy.
Ilustracja do zadania 6-6