Rozszerzenie wiedzy o metodach, wprowadzenie indeksatorów oraz mechanizmów hierarchii klas i polimorfizmu.
Student demonstruje umiejętność implementacji dziedziczenia oraz polimorfizmu poprzez tworzenie hierarchii klas z wykorzystaniem słów kluczowych virtual, override oraz base. Zadanie ma na celu pokazanie praktycznego zastosowania indeksatorów do przeszukiwania zbiorów obiektów oraz wykorzystania modyfikatorów out i params w metodach zarządzających zasobami firmy.
Zostałeś poproszony o przygotowanie szkieletu systemu do ewidencji sprzętu komputerowego w dużej korporacji. System musi bazować na ogólnej klasie opisującej zasób (np. model, numer seryjny), od której będą dziedziczyły klasy szczegółowe, takie jak Komputer i Serwer. Każdy typ zasobu posiada własny sposób generowania opisu technicznego, co wymaga zastosowania metod wirtualnych i ich nadpisywania w klasach pochodnych. Dodatkowo musisz stworzyć klasę KontrolerZasobow, która będzie pełniła rolę kontenera na obiekty i udostępni wygodny dostęp do nich za pomocą indeksatora (wyszukiwanie po numerze seryjnym lub indeksie). System powinien również oferować metodę statyczną do szybkiego liczenia statystyk, wykorzystując parametr out do zwrócenia wielu wyników naraz (np. liczbę serwerów i stacji roboczych). Kolejnym wymaganiem jest funkcja do masowego dodawania tagów opisowych do konkretnego zasobu, w której wykorzystasz parametr params. Program w konsoli powinien utworzyć kilka zróżnicowanych obiektów, dodać je do kontrolera, a następnie przeprowadzić serię testów: wyszukanie zasobu, zmianę jego statusu oraz wyświetlenie polimorficznego raportu. Całość rozwiązania musi być odporna na typowe błędy i stanowić modelowy przykład zastosowania czystego kodu obiektowego w C#.
using System; class Zasob { public string Model { get; set; } public string SN { get; set; } public Zasob(string model, string sn) { Model = model; SN = sn; } public virtual void PokazInfo() => Console.WriteLine($"Zasób: {Model}, SN: {SN}"); } class Komputer : Zasob { public string Procesor { get; set; } public Komputer(string model, string sn, string cpu) : base(model, sn) { Procesor = cpu; } public override void PokazInfo() => Console.WriteLine($"PC: {Model}, CPU: {Procesor}"); } class Serwer : Zasob { public int LiczbaDyskow { get; set; } public Serwer(string model, string sn, int dyski) : base(model, sn) { LiczbaDyskow = dyski; } public override void PokazInfo() => Console.WriteLine($"Serwer: {Model}, Dyski: {LiczbaDyskow}"); } class KontrolerZasobow { private Zasob[] dane = new Zasob[10]; private int licznik = 0; public void Dodaj(Zasob z) { if (licznik < dane.Length) dane[licznik++] = z; } // Indeksator public Zasob this[int i] { get { if (i < 0 || i >= licznik) { Console.WriteLine("Błąd: Pozycja poza zakresem."); return null; } return dane[i]; } } // Metoda z params public static void DodajTagi(string model, params string[] tagi) { Console.WriteLine($"Zasób {model} otrzymał tagi: {string.Join(", ", tagi)}"); } // Metoda statyczna z out public static void ObliczStatystyki(Zasob[] lista, out int kompy, out int serwery) { kompy = 0; serwery = 0; foreach (var z in lista) { if (z is Komputer) kompy++; else if (z is Serwer) serwery++; } } } class Program { static void Main() { KontrolerZasobow kz = new KontrolerZasobow(); kz.Dodaj(new Komputer("Dell XPS", "SN001", "i9")); kz.Dodaj(new Serwer("HP ProLiant", "SN002", 8)); kz.Dodaj(new Zasob("Monitor LG", "SN003")); Console.WriteLine("--- Raport Polimorficzny ---"); for(int i=0; i < 3; i++) { var z = kz[i]; z?.PokazInfo(); } Console.WriteLine("\n--- Test Params ---"); KontrolerZasobow.DodajTagi("Dell XPS", "BIURO", "IT-DEPT", "PILNE"); int c, s; Zasob[] testowaTablica = { new Komputer("A","1","i3"), new Serwer("B","2",4) }; KontrolerZasobow.ObliczStatystyki(testowaTablica, out c, out s); Console.WriteLine($"Statystyki: Komputery: {c}, Serwery: {s}"); } }
Zapoznanie studenta z nietypowymi sposobami przekazywania argumentów do metod w C#. Celem zadania jest opanowanie zwracania wielu wyników jednocześnie oraz projektowanie metod akceptujących dowolną liczbę parametrów wejściowych.
Zlecono Ci przygotowanie zestawu metod pomocniczych dla silnika obliczeniowego, który jest wykorzystywany przez programistów w innym dziale. Pierwsza metoda ma za zadanie podzielić dwie liczby całkowite, ale musi jednocześnie zwrócić iloraz oraz resztę z dzielenia przy użyciu modyfikatora out. Takie podejście pozwala na uniknięcie pisania dwóch oddzielnych metod dla jednej operacji matematycznej. Kolejnym Twoim zadaniem jest stworzenie uniwersalnego sumatora, który przyjmuje dowolną liczbę liczb całkowitych (typ int) jako parametry, wykorzystując słowo kluczowe params. Dzięki temu użytkownik silnika będzie mógł wywołać metodę przekazując jej dwie, pięć lub nawet zero liczb bez konieczności jawnego tworzenia tablicy. Musisz zadbać o poprawną obsługę błędów, na przykład próbę dzielenia przez zero w pierwszej metodzie. Program w konsoli powinien zaprezentować działanie obu metod dla różnych przypadków testowych. Finalne rozwiązanie ma być proste, szybkie i zgodne ze standardami projektowania metod w środowisku .NET.
KalkulatorZaawansowany – nazwa powinna odzwierciedlać przeznaczenie klasy, czyli zaawansowane operacje matematyczne.DzielenieZReszta – musi przyjmować dokładnie dwa parametry wejściowe (dzielna i dzielnik typu int) oraz dwa parametry wyjściowe oznaczone słowem kluczowym out.out muszą być przypisane wewnątrz metody przed jej zakończeniem – kompilator wymusi to, więc zawsze przypisz obie wartości (iloraz i resztę) w każdym scenariuszu.if (dzielnik == 0) i w takim przypadku ustaw iloraz na 0 oraz poinformuj o błędzie, np. przez Console.WriteLine lub wyjątek.dzielna % dzielnik, a ilorazu: iloraz = dzielna / dzielnik.SumujWiele zastosuj składnię params int[] liczby jako ostatni parametr metody – params musi być ostatnim parametrem w sygnaturze.params tworzy tablicę automatycznie, więc użytkownik może przekazać argumenty w dowolnej ilości: SumujWiele(1, 2, 3) lub SumujWiele(new int[] {1, 2, 3}).Main przy wywołaniu metody z out musisz zainicjalizować zmienne docelowe przed wywołaniem lub użyć out z nowymi zmiennymi: DzielenieZReszta(10, 3, out int iloraz, out int reszta).SumujWiele z różną liczbą argumentów: zero argumentów, jeden argument, pięć argumentów – upewnij się, że działa poprawnie w każdym przypadku.Console.WriteLine($"Iloraz: {iloraz}, Reszta: {reszta}") aby użytkownik wiedział, co oznaczają poszczególne wartości.Zrozumienie mechanizmu indeksowania obiektów jako sposobu na imitowanie zachowania tablicy wewnątrz własnej klasy. Student uczy się pisać akcesory get i set dla indeksatorów oraz zarządzać bezpiecznym dostępem do kolekcji wewnętrznej.
Zarządzasz bazą danych części zamiennych w warsztacie samochodowym. Zamiast operować na surowej tablicy stringów, chcesz stworzyć klasę "Magazyn", która w sposób inteligentny zarządza zapasami. Twoim zadaniem jest dodanie do tej klasy indeksatora, który pozwoli na pobranie lub zmianę nazwy części za pomocą czytelnej składni: magazyn[3] = "Świeca zapłonowa". Indeksator powinien weryfikować, czy podany indeks mieści się w dopuszczalnym zakresie, i informować o błędzie, jeśli użytkownik spróbuje wyjść poza granice zadeklarowanej pamięci. Możesz również spróbować zaimplementować drugi, przeciążony indeksator, który pozwala wyszukać numer półki na podstawie nazwy części (indeksowanie stringiem). Program w konsoli powinien umożliwić użytkownikowi wypełnienie magazynu kilkoma pozycjami, a następnie ich szybką modyfikację poprzez wspomniany indeksator. To podejście znacznie upraszcza czytanie kodu i sprawia, że Twoje obiekty zachowują się jak natywne kolekcje języka C#. Finalnie system powinien wyświetlić listę wszystkich dostępnych części sformatowaną w kolumnach z odpowiadającymi im numerami indeksów.
Magazyn prywatną tablicę stringów, np. private string[] dostepneCzesci = new string[10] – rozmiar 10 to minimum, ale możesz użyć większego.this oznacza, że to właśnie jest indeksator.null lub komunikat o błędzie.Main od razu po utworzeniu obiektu przypisz kilka wartości, np. magazyn[0] = "Olej silnikowy"; – indeksator zachowuje się jak tablica.Console.WriteLine($"{i}: {magazyn[i]}") aby wynik był czytelny i łatwy do interpretacji przez użytkownika.Opanowanie mechanizmu dziedziczenia oraz poprawnego wywoływania konstruktorów bazowych za pomocą base. Student uczy się hierarchicznego modelowania danych oraz reużywania kodu z klas nadrzędnych.
Tworzysz system sterowania dla nowoczesnego garażu wielopoziomowego. Musisz przygotować strukturę klas opisującą różne typy pojazdów, zaczynając od ogólnej klasy "Pojazd", która posiada markę oraz typ napędu. Następnie stwórz klasę pochodną "Samochod", która dziedziczy wszystkie te cechy, ale dodaje od siebie unikalne pole, takie jak liczba drzwi czy pojemność bagażnika. Ważnym elementem jest to, aby konstruktor samochodu nie powielał logiki przypisywania marki, lecz przekazywał te dane „do góry" – do klasy nadrzędnej przy użyciu słowa base. Dzięki temu zapewnisz, że każde auto jest przede wszystkim poprawnym obiektem typu pojazd. Twoim zadaniem jest również nadpisanie podstawowej metody wyświetlającej informacje o obiekcie tak, aby samochód prezentował dodatkowe, specyficzne dla niego parametry. Program w konsoli musi utworzyć egzemplarz ogólnego pojazdu oraz egzemplarz konkretnego samochodu i porównać ich zachowanie. Takie podejście pozwala na łatwe dodawanie w przyszłości nowych typów transportu, np. motocykli czy ciężarówek, bez modyfikacji istniejącego silnika systemu. Finalnie wyświetl zestawienie obu obiektów, zwracając uwagę na różnice w ich opisach.
Pojazd – zdefiniuj w niej dwa pola: Marka (string) i TypPaliwa (string) jako właściwości lub pola publiczne.virtual, np. public virtual void PrzedstawSie() – bez virtual nie można jej będzie nadpisać w klasach pochodnych.Samochod – to pole jest specyficzne tylko dla samochodu, a nie dla innych pojazdów.Samochoda musi mieć trzy parametry: markę, typ paliwa i liczbę drzwi – wywołaj : base(marka, typPaliwa) w pierwszej linii konstruktora.PrzedstawSie() w klasie Samochod używając override: public override void PrzedstawSie() – dzięki temu wywołanie na obiekcie typu Pojazd uruchomi właściwą wersję.Samochod rozważ użycie base.PrzedstawSie() na początku, aby najpierw wyświetlić informacje dziedziczone, a potem dopisać specyficzne.Main możesz tworzyć obiekty na dwa sposoby: Pojazd p = new Pojazd(...) oraz Samochod s = new Samochod(...) – oba są poprawne.Samochod do zmiennej typu Pojazd: Pojazd p = new Samochod(...); a następnie wywołaj p.PrzedstawSie() – powinna wyświetlić się wersja z Samochodu.Main.Praktyczne zastosowanie polimorfizmu do rozwiązywania problemów biznesowych. Student uczy się, jak jedna metoda wirtualna zadeklarowana w klasie bazowej może zachowywać się w różny sposób w zależności od rzeczywistego typu obiektu.
W dziale księgowości dużej firmy system naliczania premii rocznych działa według różnych reguł dla różnych stanowisk. Każdy pracownik jest reprezentowany przez klasę "Pracownik", która posiada metodę wirtualną o nazwie ObliczPremie. Dla przeciętnego pracownika biurowego premia wynosi stałe 10% wynagrodzenia bazowego. Musisz jednak stworzyć dwie klasy pochodne: "Dyrektor" oraz "Sprzedawca". Dla dyrektora premia powinna być powiększona o dodatkowy bonus stały za wyniki pionu, natomiast dla sprzedawcy jest ona uzależniona od obrotu, jaki wygenerował w ciągu roku. Twoim zadaniem jest nadpisanie metody ObliczPremie w tych klasach w taki sposób, aby każdy obiekt „wiedział", jak policzyć własną premię. Kluczowym elementem zadania jest utworzenie tablicy typu Pracownik, w której umieścisz obiekty różnych klas (dyrektora i pracownika biurowego obok siebie), a następnie wywołanie metody obliczeniowej w jednej wspólnej pętli. System powinien automatycznie dopasować algorytm do typu osoby bez konieczności sprawdzania tego w kodzie za pomocą instrukcji if. To zadanie demonstruje potęgę polimorfizmu w zarządzaniu złożonymi procesami biznesowymi.
Pracownik zdefiniuj pole Wynagrodzenie jako typ decimal (nie double czy int) – decimal jest zalecany do operacji finansowych ze względu na precyzję.virtual: public virtual decimal ObliczPremie() – bez tego słowa kluczowego nie będzie można jej nadpisać.Dyrektor powinna dziedziczyć po Pracownik i mieć dodatkowe pole, np. DodatekDyrektorski typu decimal – to stała kwota bonusu.Dyrektora rozważ użycie base.ObliczPremie() aby nie powielać obliczeń: return base.ObliczPremie() + DodatekDyrektorski;Sprzedawca powinna mieć pole ObrotyRoczne (decimal) – premia sprzedawcy może być obliczana procentowo od obrotów lub jako 20% pensji.Main zadeklaruj tablicę typu bazowego: Pracownik[] pracownicy = new Pracownik[3]; – możesz przechowywać obiekty różnych typów w jednej tablicy.Zrozumienie subtelnej, ale kluczowej różnicy między nadpisywaniem metod (override) a ich przesłanianiem (new). Student uczy się świadomego zarządzania nazewnictwem metod w hierarchii oraz dowiaduje się, jak wybór słowa kluczowego wpływa na wywołania polimorficzne.
Wyobraź sobie, że piszesz system diagnostyczny dla dwóch typów urządzeń: standardowych sensorów oraz nowoczesnych czujników laserowych. Klasa bazowa "Sensor" posiada metodę Testuj(), która jest wirtualna. Klasa pochodna "SensorNowoczesny" nadpisuje tę metodę (override), zmieniając jej zachowanie na bardziej precyzyjne. Z kolei inna klasa, "SensorUkryty", posiada metodę Testuj() oznaczoną słowem kluczowym new, co oznacza, że świadomie przesłania ona wersję z klasy bazowej, nie biorąc udziału w mechanizmie polimorfizmu. Twoim celem jest badanie, co się stanie, gdy przypiszemy oba te obiekty do zmiennych typu bazowego Sensor i wywołamy na nich metodę diagnostyczną. Czy program uruchomi nową wersję testu, czy powróci do starej, bazowej definicji? To zadanie jest bardzo techniczne i ma na celu uniknięcie błędów w dużych frameworkach, gdzie przesłonięcie metody zamiast jej nadpisania może prowadzić do bardzo trudnych do wykrycia błędów logicznych. Program w konsoli powinien wyraźnie pokazać różnice w wywołaniach dla obu przypadków. Dzięki temu zrozumiesz, że słowo kluczowe w sygnaturze metody ma decydujący wpływ na „ścieżkę wywołania" w pamięci komputera. Finalnie opisz w komentarzu zaobserwowane rezultaty eksperymentu.
Sensor z metodą public virtual void Testuj() – słowo virtual jest obowiązkowe, aby można było nadpisywać lub przesłaniać tę metodę w klasach pochodnych.Testuj() w klasie bazowej powinna wyświetlać "Test bazowy" – to pozwoli Ci łatwo zaobserwować, która wersja się uruchamia.Sensor i nadpisuje metodę: public override void Testuj() – wypisuje "Test nowoczesny (override)".Sensor i przesłania metodę: public new void Testuj() – wypisuje "Test ukryty (new)".override całkowicie zastępuje metodę bazową w hierarchii dziedziczenia, natomiast new tylko ukrywa metodę bazową dla konkretnej zmiennej.Main stwórz obiekty przez polimorficzne referencje: Sensor s1 = new SensorNowoczesny(); oraz Sensor s2 = new SensorUkryty();SensorUkryty, musisz użyć rzutowania: ((SensorUkryty)s2).Testuj() – teraz zobaczysz "Test ukryty (new)".override gdy chcesz, aby metoda była wywoływana polimorficznie – to jest preferowane podejście w większości scenariuszy.new tylko wtedy, gdy świadomie chcesz ukryć metodę bazową i NIE chcesz polimorfizmu dla tej metody.Integracja wszystkich poznanych mechanizmów: dziedziczenia, metod wirtualnych oraz parametrów params. Student buduje kompleksowy system, który wykazuje korzyści płynące z łączenia różnych zaawansowanych technik programistycznych w C#.
Jako lider zespołu IT w firmie kurierskiej, przygotowujesz mechanizm do wyliczania kosztów transportu różnych typów paczek. Stwórz główną klasę "Przesylka" z wirtualną metodą ObliczKoszt() oraz właściwością Waga. Następnie zaimplementuj klasy "PaczkaStandardowa" i "PrzesylkaEkspresowa", które w różny sposób nadpisują algorytm cenowy (np. ekspres dodaje stałą opłatę za priorytet). Dodatkowo Twoja klasa bazowa powinna posiadać konstruktor, który przyjmuje wagę i przekazuje ją do pola chronionego (protected). Kolejnym poziomem trudności jest stworzenie klasy statycznej "Logistyka", która zawiera metodę WyslijZbiorczo. Ta metoda powinna przyjmować parametr params Przesylka[] i obliczać całkowity koszt wysłania wszystkich paczek przekazanych w jednym wywołaniu. Dzięki polimorfizmowi, metoda ta będzie działać poprawnie niezależnie od tego, czy wyślemy jej trzy paczki zwykłe, czy pięć ekspresowych. System musi wyświetlić szczegółowy raport z podróży zbiorczej, listując każdą paczkę i jej wyliczony indywidualnie koszt. Taki projekt uczy, jak budować elastyczne interfejsy programistyczne odporne na zmiany wymagań biznesowych. Program kończy się wyświetleniem wielkiego podsumowania dla całej floty wysyłkowej.
Przesylka – zdefiniuj innej pole protected double waga (protected oznacza, że jest dostępne w klasach pochodnych, ale nie na zewnątrz).Przesylka przyjmujący wagę jako parametr i przypisujący ją do pola: public Przesylka(double waga) { this.waga = waga; }Logistyka powinna być static: public static class Logistyka – klasy statycznej nie można instancjonować, używa się jej bezpośrednio.Main wywołaj metodę bez tworzenia tablicy: Logistyka.PodsumujWszystko(new PaczkaStandardowa(5), new PrzesylkaEkspresowa(3), new PaczkaStandardowa(10)); – parametr params pozwala na taką składnię.