INF.04 · C# · lekcja 17

Klasy generyczne (szablony klas)

Jeden pojemnik na różne typy

  • Zrozum
  • Sprawdź
  • Napisz sam

Po tej lekcji: jeden pojemnik na różne typy

Dzisiaj zrobisz konkretną rzecz

Generyki to mechanizm, z którego korzystałeś już wielokrotnie (List, Dictionary), nie wiedząc jeszcze, jak dokładnie działa — ta lekcja odsłania tę "kuchnię" i pokazuje, jak tworzyć własne, elastyczne klasy działające dla dowolnego typu danych, bez powielania kodu.

Uniwersalne pudełko transportowe w magazynie może pomieścić różne rodzaje towarów (książki, elektronikę, ubrania) — sam mechanizm pakowania i transportu jest identyczny, zmienia się tylko to, CO jest w środku. To dokładnie idea klasy generycznej.

Sprawdź w działaniu

Jeden pojemnik na różne typy

Najpierw przewidź wynik. Zmień dane w modelu i porównaj efekt. Spróbuj także przypadku granicznego, a nie tylko pierwszego poprawnego przykładu.

Ta interakcja wymaga włączonego JavaScriptu.

Model pokazuje wybraną zasadę. Nie uruchamia ani nie ocenia wklejonego kodu C#.

1. Przygotuj i uruchom własny projekt

2. Najpierw mały przykład

Wykonaj tę próbę przed dłuższym rozwiązaniem. T jest parametrem typu. Pudelko<int> i Pudelko<string> powstają z tej samej definicji, ale kompilator pilnuje właściwych typów. To nie zamiana wszystkiego na object.

Program.cscsharp
var liczby = new Pudelko<int>(7);
var napisy = new Pudelko<string>("Ola");
Console.WriteLine(liczby.Wartosc);
Console.WriteLine(napisy.Wartosc);

class Pudelko<T>
{
    public T Wartosc { get; }
    public Pudelko(T wartosc) { Wartosc = wartosc; }
}
Wynik
7
Ola
  1. Co sprawdzić? Zmień jedną wartość i przed uruchomieniem zapisz przewidywany rezultat. Porównaj go z opisem wyniku; przy błędzie wróć do konkretnej instrukcji.

Jak to działa — 1

W Lekcji 8 korzystałeś z List<string> i Dictionary<string, int> — te "ostre nawiasy" z typem w środku to GENERYKI (generics), mechanizm, który teraz nauczysz się tworzyć SAMODZIELNIE.

Klasa generyczna to szablon klasy, który działa dla RÓŻNYCH typów danych, bez pisania osobnej wersji klasy dla każdego typu z osobna. Zamiast konkretnego typu (np. int), w definicji klasy używasz "placeholdera" — zwyczajowo nazywanego T (od "Type"): class Pojemnik<T> { private T zawartosc; public void Ustaw(T wartosc) { zawartosc = wartosc; } public T Pobierz() { return zawartosc; } }

Tej samej klasy Pojemnik<T> możesz użyć dla DOWOLNEGO typu, podając go przy tworzeniu obiektu: Pojemnik<int> pojemnikLiczb = new Pojemnik<int>(); Pojemnik<string> pojemnikTekstow = new Pojemnik<string>();

Dlaczego to lepsze niż kopiowanie klasy dla każdego typu? Bez generyków musiałbyś napisać osobno PojemnikInt, PojemnikString, PojemnikFilm — ten sam kod, powtórzony wielokrotnie, tylko z innym typem. Generyki pozwalają napisać logikę RAZ i używać jej dla dowolnego typu, zachowując przy tym pełne bezpieczeństwo typów (kompilator nie pozwoli wstawić string do Pojemnik<int>).

Jak to działa — 2

Dokładnie w ten sposób zbudowane są List<T> i Dictionary<TKey, TValue>, których używałeś już wcześniej — teraz wiesz, jak działają "od środka" i potrafisz stworzyć własny, podobny mechanizm.

3. Pełny przykład — czytaj od Main

Teraz zastąp Program.cs tym programem. Zanim uruchomisz, znajdź dane wejściowe, operację i miejsce prezentacji wyniku. W tym wariancie zachowujemy jawny Main. Nie wklejaj kilku pełnych programów do jednego pliku.

Program.cs — osobne uruchomieniecsharp
using System;

class Para<T1, T2> // klasa generyczna z DWOMA parametrami typu
{
    public T1 Pierwszy;
    public T2 Drugi;

    public Para(T1 pierwszy, T2 drugi)
    {
        Pierwszy = pierwszy;
        Drugi = drugi;
    }

    public void Wyswietl()
    {
        Console.WriteLine("(" + Pierwszy + ", " + Drugi + ")");
    }
}

class Program
{
    static void Main()
    {
        // ta sama klasa Para<T1, T2> działa dla RÓŻNYCH kombinacji typów
        Para<string, int> filmOcena = new Para<string, int>("Diuna", 9);
        filmOcena.Wyswietl(); // (Diuna, 9)

        Para<string, double> produktCena = new Para<string, double>("Bilet", 24.99);
        produktCena.Wyswietl(); // (Bilet, 24.99)

        Para<int, int> wspolrzedne = new Para<int, int>(10, 20);
        wspolrzedne.Wyswietl(); // (10, 20)
    }
}
  1. Prześledź kod class Para<T1, T2> deklaruje klasę generyczną z DWOMA niezależnymi parametrami typu — przy tworzeniu obiektu (new Para<string, int>(...)) podajesz konkretne typy, którymi T1 i T2 zostaną zastąpione dla TEGO KONKRETNEGO obiektu. Kompilator pilnuje zgodności typów — próba filmOcena.Pierwszy = 5; (liczba zamiast tekstu) zgłosiłaby błąd kompilacji, bo T1 zostało ustalone jako string.

Sprawdź, zanim przepiszesz cały program

Częsty błąd to próba wykonania na parametrze typu T operacji, których nie każdy typ obsługuje (np. T + T nie zadziała, bo nie każdy typ da się dodawać operatorem +) — generyki bez dodatkowych ograniczeń (constraints, temat wykraczający poza ten kurs) muszą działać dla KAŻDEGO możliwego typu. Drugi błąd: mylenie nazwy parametru typu T z prawdziwą, konkretną klasą — T to tylko "placeholder", zastępowany dopiero przy użyciu klasy generycznej. Trzeci: tworzenie osobnych, niemal identycznych klas dla różnych typów zamiast jednej klasy generycznej — to dokładnie problem, który generyki mają rozwiązać.

Ćwiczenie z pomocą · samodzielna praca

4. Teraz Twoja zmiana

Zdefiniuj prostą klasę generyczną Pojemnik<T> z metodami Ustaw(T wartosc) i Pobierz(), i użyj jej dla typu int oraz string.

Oczekiwany rezultat
Spełnij warunki polecenia. Zapisz wejście, otrzymany wynik i wyjaśnij, która część kodu za niego odpowiada.
Potrzebujesz pomocy? Rozwiązuj krok po kroku.
Krok 1 — pokaż podpowiedź

Wróć do podstawowej formy oraz objaśnienia: T jest parametrem typu. Pudelko<int> i Pudelko<string> powstają z tej samej definicji, ale kompilator pilnuje właściwych typów. To nie zamiana wszystkiego na object.

Krok 2 — pokaż podpowiedź

Zmień tylko część potrzebną do polecenia. Zachowaj działającą wersję przed zmianą i uruchamiaj po każdym małym etapie.

Krok 3 — pokaż podpowiedź

Sprawdź przypadek zwykły i graniczny; porównaj z regułą: 7 Ola

Otwórz tylko tyle podpowiedzi, ile potrzebujesz. Po każdym kroku wróć do zadania i sprawdź wynik.

5. Sprawdź rozumienie, nie tylko uruchomienie

Dalej na swoim koncie

Rozgrzewki, quiz i zadania są w biurku ucznia.

Teoria jest otwarta dla wszystkich. Żeby rozwiązywać zadania, zapisywać szkice i widzieć swój postęp, zaloguj się.

Zaloguj się →
Źródła i dalsza nauka