01
Zadanie wprowadzające: System Obsługi Zgłoszeń Technicznych
Cel

Student demonstruje umiejętność łączenia zaawansowanych kolekcji generycznych (Dictionary, List) z mechanizmami bezpiecznej obsługi błędów. Zadanie ma na celu pokazanie praktycznego zastosowania własnych klas wyjątków oraz wykorzystania technologii LINQ do szybkiego filtrowania i agregacji danych w strukturach słownikowych. Student rozumie, jak zarządzać cyklem życia zgłoszenia w systemie informatycznym, dbając o spójność danych.

Scenariusz

Zostałeś oddelegowany do stworzenia modułu serwerowego dla centrum zgłoszeń technicznych w firmie telekomunikacyjnej. System musi przechowywać aktywne zgłoszenia w strukturze słownikowej (Dictionary), gdzie kluczem jest unikalny identyfikator zgłoszenia, a wartością obiekt klasy Zgloszenie. Każde zgłoszenie posiada listę (List) wpisów w historii, dokumentującą kroki podjęte przez techników. Twoim zadaniem jest zapewnienie, aby każda próba odczytu nieistniejącego zgłoszenia lub próba przypisania błędnego statusu kończyła się wyrzuceniem dedykowanego wyjątku SerwisException. Program w konsoli powinien umożliwić dodawanie zgłoszeń, aktualizację ich opisów oraz generowanie raportów statystycznych. Musisz wykorzystać LINQ, aby w ułamku sekundy odnaleźć wszystkie zgłoszenia o statusie "Pilne" lub te, które nie były aktualizowane od dłuższego czasu. Całość aplikacji musi być zabezpieczona blokami try-catch, które w sposób czytelny informują operatora o przyczynie problemu, na przykład o dublowaniu się numerów ID. Projekt ten stanowi fundament pod budowę systemów klasy Helpdesk i Service Management. Finalny kod musi gwarantować, że nawet w sytuacjach awaryjnych system nie ulegnie zawieszeniu i wyświetli precyzyjne logi diagnostyczne.

Sugerowane kroki do wykonania
  1. Zdefiniuj własną klasę wyjątku o nazwie SerwisException dziedziczącą po klasie Exception.
  2. Stwórz prostą klasę Zgloszenie z polami: Id, Tytul, Status (string) oraz List<string> Historia.
  3. W klasie głównej zainicjalizuj słownik Dictionary<int, Zgloszenie> przechowujący bazę danych.
  4. Opracuj metodę DodajZgloszenie, która sprawdza czy dany klucz już istnieje w słowniku.
  5. Jeśli klucz istnieje, rzuć wyjątek SerwisException z odpowiednim komunikatem.
  6. Stwórz metodę do pobierania zgłoszenia, która w przypadku braku ID również rzuca autorski wyjątek.
  7. W Main otocz operacje dodawania danych blokiem try-catch, aby przechwycić błędy serwisu.
  8. Dodaj kilka zgłoszeń testowych do słownika, w tym jedno z duplikatorem ID dla testu wyjątku.
  9. Zastosuj LINQ (metodę .Where()) do przefiltrowania słownika i wybrania tylko zgłoszeń o konkretnym statusie.
  10. Wyświetl przefiltrowaną listę na konsoli, dbając o estetyczne formatowanie rekordów.
Kod rozwiązania
using System;
using System.Collections.Generic;
using System.Linq;

// Własny wyjątek
class SerwisException : Exception 
{
    public SerwisException(string msg) : base(msg) { }
}

class Zgloszenie
{
    public int Id { get; set; }
    public string Opis { get; set; }
    public string Status { get; set; }
}

class Program
{
    static Dictionary<int, Zgloszenie> baza = new Dictionary<int, Zgloszenie>();

    static void Dodaj(int id, string opis)
    {
        if (baza.ContainsKey(id))
            throw new SerwisException($"ID {id} już istnieje w systemie!");
        
        baza.Add(id, new Zgloszenie { Id = id, Opis = opis, Status = "Nowe" });
    }

    static void Main()
    {
        try
        {
            Dodaj(101, "Awaria routera");
            Dodaj(102, "Błąd logowania");
            Dodaj(101, "Duplikat"); // Tu wystąpi błąd
        }
        catch (SerwisException ex)
        {
            Console.WriteLine($"BŁĄD SYSTEMU: {ex.Message}");
        }
        finally
        {
            Console.WriteLine("Zakończono operację na słowniku.");
        }

        // Prosty LINQ
        var wyniki = baza.Values.Where(z => z.Opis.Contains("Awaria"));
        
        Console.WriteLine("\nWyniki wyszukiwania:");
        foreach (var z in wyniki) 
            Console.WriteLine($"[{z.Id}] {z.Opis} ({z.Status})");
    }
}
                    
5.1
Kalkulator Odporny na Błędy (Try-Catch-Finally)
Cel

Zapoznanie studentów z hierarchią wyjątków wbudowanych w język C# oraz strukturą blokową do obsługi sytuacji awaryjnych. Student uczy się pisać kod, który nie przerywa działania przy błędnych danych wejściowych od użytkownika.

Scenariusz

Wyobraź sobie, że pracujesz nad prostym kalkulatorem dzielenia dla uczniów szkoły podstawowej. Aplikacja musi pobrać dwie liczby od użytkownika i wyświetlić wynik ich dzielenia. Ponieważ użytkownicy mogą wpisać zamiast liczb dowolny tekst lub próbować dzielić przez zero, Twój program musi być na to w pełni przygotowany. Musisz zastosować blok try-catch z wieloma sekcjami catch, aby osobno obsłużyć błąd formatu (FormatException) oraz błąd matematyczny (DivideByZeroException). Niezależnie od tego, czy obliczenie zakończyło się sukcesem, czy błędem, system musi wyświetlić komunikat o gotowości do kolejnego zadania za pomocą bloku finally. Jest to doskonała okazja do zrozumienia, jak istotna jest segregacja błędów w celu dostarczenia precyzyjnej odpowiedzi użytkownikowi. Program w konsoli powinien działać tak długo, aż użytkownik poda poprawne dane lub zdecyduje się wyjść. Twoim zadaniem jest zapewnienie stabilności aplikacji nawet w ekstremalnych przypadkach. Finalnie wyświetl informację o tym, który konkretnie wyjątek został przechwycony przez system w danej turze obliczeniowej.

Sugerowane kroki do wykonania
  1. Utwórz pętlę while(true), aby umożliwić wielokrotne próby obliczeń.
  2. Otwórz blok try na początku pętli.
  3. Poproś użytkownika o podanie dzielnej i dzielnika za pomocą Console.ReadLine().
  4. Skonwertuj wejścia na typ int przy użyciu int.Parse(input).
  5. Wykonaj dzielenie i wyświetl wynik całoliczbowy.
  6. Dodaj blok catch (FormatException), który wypisze ostrzeżenie o nieprawidłowym znaku.
  7. Dodaj blok catch (DivideByZeroException), który poinformuje o zakazie dzielenia przez 0.
  8. Zaimplementuj blok finally, wypisujący kreskę oddzielającą kolejną próbę.
  9. Zastosuj słowo kluczowe break, aby zakończyć pętlę po udanym obliczeniu.
  10. Przetestuj program wpisując litery zamiast cyfr, aby wywołać błąd formatowania.
5.2
Dynamiczna Lista Gości Hotelowych (List<T>)
Cel

Opanowanie pracy z podstawową kolekcją generyczną List<T>. Student uczy się dynamicznej alokacji pamięci na zbiory obiektów oraz poznaje wbudowane metody manipulacji listą, takie jak dodawanie, usuwanie i sortowanie.

Scenariusz

Zlecono Ci przygotowanie modułu do zarządzania listą meldunkową w małym pensjonacie. Zamiast sztywnej tablicy, musisz użyć generycznej listy, aby móc swobodnie dodawać nowych gości w trakcie trwania doby hotelowej. Każdy wpis na liście to zwykły ciąg znaków (imię i nazwisko). Twoim zadaniem jest stworzenie prostego menu, które pozwala na : dodanie nowego gościa, usunięcie osoby (jeśli się wymeldowała) oraz wyświetlenie wszystkich aktualnie zameldowanych osób w porządku alfabetycznym. System powinien również umożliwiać szybkie sprawdzenie, czy konkretna osoba znajduje się już w hotelu, korzystając z metody Contains(). Musisz zadbać o to, aby lista nie zawierała pustych wpisów i była zawsze aktualna. To ćwiczenie uczy, dlaczego listy są znacznie wygodniejsze od tablic w sytuacjach, gdy nie znamy z góry liczby rekordów. Program w konsoli powinien działać w interaktywny sposób, reagując na komendy użytkownika. Na koniec dnia menedżer powinien móc wyczyścić całą listę jednym poleceniem Clear(). Finalny raport musi zawierać aktualną liczbę gości (właściwość Count) przed i po sortowaniu.

Sugerowane kroki do wykonania
  1. Zadeklaruj listę generyczną typu string: List<string> goscie = new List<string>().
  2. Uruchom pętlę przeznaczoną na proste menu tekstowe (1-Dodaj, 2-Pokaz, 3-Usun).
  3. Użyj metody Add(), aby dopisać nazwisko podane przez użytkownika.
  4. Użyj metody Sort(), aby uporządkować listę przed jej wyświetleniem.
  5. Zastosuj metodę Remove(), aby usunąć konkretny wpis na podstawie jego treści.
  6. Wyświetl aktualną liczbę gości korzystając z goscie.Count.
  7. Wypisz listę w pętli foreach, numerując każdą pozycję (np. 1. Kowalski).
  8. Zaimplementuj wyszukiwarkę korzystającą z metody Contains(), aby sprawdzić dostępność pokoju.
  9. Zabezpiecz dodawanie gości przed pustymi ciągami znaków (String.IsNullOrWhiteSpace).
  10. Przetestuj dodanie 5 osób i usunięcie tej środkowej, sprawdzając jak lista się „przesuwa”.
5.3
Katalog Księgarni: Słownik ISBN (Dictionary)
Cel

Zrozumienie działania par klucz-wartość w generycznym słowniku Dictionary<K, V>. Student uczy się projektowania struktur umożliwiających błyskawiczne wyszukiwanie danych bez konieczności przeglądania całego zbioru.

Scenariusz

W dużym antykwariacie każda książka posiada unikalny kod kreskowy ISBN, który służy jako klucz do bazy danych. Twoim zadaniem jest stworzenie słownika, który jako klucz przyjmuje ten kod (string), a jako wartość przechowuje pełny tytuł książki. Musisz przygotować system, który pozwala na szybkie dodawanie nowych pozycji oraz natychmiastowe sprawdzanie, jaki tytuł kryje się pod danym kodem. Wyszukiwanie w słowniku jest znacznie wydajniejsze niż w liście, co poczujesz przy próbie odszukania rekordu wśród kilkunastu wpisów. Twoja aplikacja powinna obsługiwać mechanizm sprawdzania czy klucz już istnieje za pomocą metody TryGetValue() lub ContainsKey(), aby uniknąć błędów przy próbie dublowania kodów. Użytkownik w konsoli wpisuje kod, a program odpowiada nazwą książki lub informacją o braku w katalogu. To zadanie uczy, jak dobierać struktury danych do konkretnych problemów inżynierskich – w tym przypadku szybkiego dostępu po unikalnym identyfikatorze. Finalnie program powinien wyświetlić wszystkie kody i tytuły w formie zestawienia tabelarycznego na ekranie. Jest to świetny wstęp do pracy z profesjonalnymi systemami bazodanowymi.

Sugerowane kroki do wykonania
  1. Zadeklaruj słownik generyczny Dictionary<string, string> ksiazki.
  2. Dodaj do słownika parę przykładowych pozycji przy użyciu metody Add().
  3. Poproś użytkownika o wpisanie kodu ISBN i spróbuj go odnaleźć.
  4. Użyj warunku if (ksiazki.ContainsKey(kod)), aby bezpiecznie sprawdzić obecność klucza.
  5. W przypadku znalezienia, wypisz wartość przypisaną do wejścia: ksiazki[kod].
  6. Zaimplementuj opcję usuwania wpisu na podstawie klucza (metoda Remove).
  7. Użyj pętli foreach i typu KeyValuePair<string, string> do wyświetlenia całego katalogu.
  8. Dodaj informację o wielkości słownika na końcu raportu.
  9. Pamiętaj o obsłużeniu wyjątków, gdy użytkownik poda null jako klucz.
  10. Przetestuj program dodając dwa różne klucze o tej samej nazwie (powinien wystąpić błąd w Add).
5.4
Własne Wyjątki: System Autoryzacji Kont
Cel

Opanowanie techniki tworzenia i zgłaszania (wyrzucania) własnych klas wyjątków dopasowanych do domeny biznesowej aplikacji. Student uczy się komunikować specyficzne błędy logiczne w sposób ustrukturyzowany.

Scenariusz

Budujesz system zabezpieczeń dla firmowego intranetu, który musi weryfikować poprawność haseł i długość loginów. Twoim zadaniem jest stworzenie klasy wyjątku o nazwie "SecurityException", która będzie niosła informację o typie naruszenia zasad bezpieczeństwa. W klasie głównej napisz metodę WeryfikujDostep, która przyjmuje login oraz hasło jako argumenty. Jeśli hasło jest krótsze niż 6 znaków lub login zawiera niedozwolone symbole, metoda powinna rzucić Twój autorski wyjątek za pomocą słowa kluczowego throw. Program w konsoli symuluje proces rejestracji konta, gdzie użytkownik wpisuje dane, a system przekazuje je do weryfikatora wewnątrz bloku try. Jeśli wystąpi naruszenie, blok catch przechwyci Twój konkretny wyjątek i wyświetli na czerwono przyczynę odrzucenia wniosku. To zadanie uczy, jak samodzielnie definiować kontrakty bezpieczeństwa i w elegancki sposób przesyłać informacje o ich złamaniu „w górę” stosu wywołań. Finalny program powinien potwierdzić pomyślne utworzenie konta tylko wtedy, gdy wszystkie testy zakończą się sukcesem. Jest to kluczowy element przy tworzeniu bezpiecznego oprogramowania klasy enterprise.

Sugerowane kroki do wykonania
  1. Zdefiniuj klasę SecurityException dziedziczącą po klasie Exception.
  2. Dodaj w niej konstruktor przyjmujący wiadomość i przekazujący ją do : base(wiadomosc).
  3. Dodaj w niej dodatkową właściwość GodzinaBlędu (DateTime.Now).
  4. Zaimplementuj metodę Logowanie(string login, string haslo).
  5. Wewnatrz metody sprawdź długość hasła. Jeśli < 8, rzuć throw new SecurityException(...).
  6. W Main wywołaj Logowanie wewnatrz bloku try-catch.
  7. W bloku catch przechwyć konkretnie SecurityException ex.
  8. Wypisz z wyjątku wiadomość oraz godzinę wystąpienia problemu.
  9. Dodaj generyczny blok catch (Exception) na wypadek innych nieprzewidzianych błędów.
  10. Dodaj log końcowy informujący o zakończeniu procedury sprawdzania.
5.5
Symulacja Kolejki w Przychodni (Queue<T>)
Cel

Poznanie zasady działania kolekcji typu kolejka (FIFO – First In, First Out). Student uczy się modelować procesy, w których kolejność zgłoszeń ma kluczowe znaczenie i dane są pobierane dokładnie w takiej kolejności, w jakiej zostały dodane.

Scenariusz

Zaprojektuj system zarządzania ruchem pacjentów w rejestracji medycznej. Musisz wykorzystać kolekcję generyczną Queue<string>, aby zapewnić sprawiedliwą obsługę osób zgłaszających się do lekarza. Program powinien oferować trzy główne funkcje: zarejestrowanie pacjenta na koniec kolejki (Enqueue), wezwanie kolejnej osoby do gabinetu (Dequeue) oraz podejrzenie, kto jest następny bez usunięcia go z listy (Peek). Twoim zadaniem jest obsłużenie sytuacji, gdy lekarz prosi o kolejnego pacjenta, a poczekalnia jest pusta – program nie może się zawiesić, lecz musi wyświetlić stosowny komunikat. Każdy pacjent to po prostu nazwisko przekazywane do systemu w formie tekstowej. Użytkownik w konsoli symuluje poranny dyżur, dodając kilka osób do systemu i stopniowo "obsługując" je przez lekarza. To ćwiczenie świetnie pokazuje, jak proste struktury danych mogą zarządzać skomplikowanymi procesami społecznymi. Finalnie system powinien wyświetlić, ilu pacjentów pozostało jeszcze w kolejce na koniec zmiany. Jest to klasyczny przykład zastosowania buforów danych w informatyce.

Sugerowane kroki do wykonania
  1. Zadeklaruj kolejkę generyczną: Queue<string> pacjenci = new Queue<string>().
  2. Zaimplementuj pętlę menu z opcjami: 1-Dodaj, 2-Obsluz, 3-KtoNastepny.
  3. Użyj Enqueue(input), aby dodać pacjenta do kolejki.
  4. Przy poleceniu obsługi najpierw sprawdź właściwość Count, aby uniknąć wyjątku.
  5. Użyj Dequeue(), aby pobrać i wyświetlić pierwszą osobę z kolejki.
  6. Użyj Peek(), aby sprawdzić kto czeka przed drzwiami bez usuwania go z systemu.
  7. Wypisz stan kolejki po każdej operacji przy użyciu foreach.
  8. Pamiętaj, że w kolejce nie możemy wybierać elementów ze środka (brak indeksowania).
  9. Dodaj informację o średnim czasie oczekiwania (symulacja sumy).
  10. Przetestuj program dodając dwóch osób pod rząd i obsługując je sekwencyjnie.
5.6
Analiza Kadr z LINQ (Filtrowanie i Agregacja)
Cel

Pierwszy kontakt z technologią Language Integrated Query (LINQ). Student uczy się pisać skompaktowane zapytania do kolekcji generycznych, zastępując rozbudowane pętle for eleganckimi wywołaniami metod deklaratywnych.

Scenariusz

Otrzymałeś plik z danymi o pracownikach dużej firmy handlowej (pola: Imię, Nazwisko, Pensja, Miasto). Twoim zadaniem jest wczytanie tych informacji do listy generycznej, a następnie przeprowadzenie serii analiz statystycznych przy użyciu LINQ. Musisz w jednej linii kodu odnaleźć wszystkich pracowników, którzy zarabiają powyżej średniej krajowej i mieszkają w Krakowie. Kolejnym Twoim celem jest wyliczenie sumy wszystkich wynagrodzeń w firmie za pomocą metody Sum() oraz znalezienie osoby o najkrótszym nazwisku. Zamiast pisać wielopoziomowe pętle z warunkami if, wykorzystaj metody rozszerzające takie jak Where(), OrderBy(), oraz Select(). Program ma wyświetlić wynikowe listy w czytelny sposób, sortując pracowników alfabetycznie według nazwiska. To zadanie otwiera drzwi do nowoczesnej obróbki danych w .NET, która jest standardem w pracy z bazami danych Entity Framework. Finalnie zaprezentuj raport płacowy, który automatycznie aktualizuje się po dodaniu nowego pracownika do listy bazowej. Wykaż w sprawozdaniu, o ile mniej linii kodu wymaga LINQ w porównaniu do tradycyjnych metod wyszukiwania danych.

Sugerowane kroki do wykonania
  1. Zbuduj prostą klasę Pracownik z czterema polami.
  2. Utwórz List<Pracownik> i wypełnij ją 5 przykładowymi rekordami.
  3. Zaimportuj przestrzeń nazw System.Linq na początku pliku.
  4. Zastosuj list.Where(p => p.Pensja > 3000) do przefiltrowania listy.
  5. Dołącz metodę OrderBy(p => p.Nazwisko) w tym samym łańcuchu wywołań.
  6. Użyj metody Sum(p => p.Pensja), aby uzyskać łączny koszt wynagrodzeń.
  7. Zastosuj Count() do policzenia ile osób spełnia dany warunek.
  8. Wyświetl wyniki filtrowania w pętli foreach używając interpolacji stringów.
  9. Dodaj przykład uzycia metody FirstOrDefault(), aby znaleźć konkretną osobę po nazwisku.
  10. Opisz w komentarzu różnicę między filtrowaniem danych w Liście a w Słowniku przy użyciu LINQ.