Dataniedziela, 9 sierpnia 2026 Czas07:28:25
← Programowanie konsolowe (C#)

Lekcja 21. Struktury (struct) w C#

Średni

Po co się tego uczymy?

Podstawa programowa (INF.04.3.2) wymaga umiejętności posługiwania się "rekordami" — czyli złożonymi typami danych grupującymi kilka wartości w jedną paczkę. W C# taką rolę pełni struct. To lekki sposób na opisanie "punktu na mapie", "koloru RGB" czy "daty" bez pisania pełnej klasy — a jednocześnie świetna okazja, żeby zrozumieć różnicę między typami wartościowymi a referencyjnymi, która w C# jest fundamentalna.

Teoria

struct wygląda i zachowuje się bardzo podobnie do klasy — może mieć pola, konstruktory, metody, właściwości. Kluczowa różnica leży głębiej, w pamięci:

  • klasa to typ referencyjny — zmienna przechowuje adres obiektu na stercie (heap). Kopiując zmienną, kopiujesz tylko adres — obie zmienne wskazują na ten sam obiekt;
  • struct to typ wartościowy — zmienna przechowuje same dane bezpośrednio, zwykle na stosie (stack). Kopiując zmienną struktury, dostajesz niezależną kopię całych danych.

To ma realne konsekwencje: jeśli przekażesz obiekt klasy do metody i metoda zmieni jego pole, zmiana będzie widoczna także poza metodą (bo to wciąż ten sam obiekt). Jeśli przekażesz strukturę, metoda dostaje kopię — zmiany w niej nie wpłyną na oryginał.

Kiedy używać struct, a kiedy class? Struktury sprawdzają się dla małych, niezmiennych (albo rzadko zmienianych) paczek danych bez własnej "tożsamości" — punkt (x, y), kolor (r, g, b), zakres dat. Klasy sprawdzają się tam, gdzie obiekt ma zachowanie, cykl życia, dziedziczenie albo powinien być współdzielony między różnymi częściami programu. W praktyce na start i na egzaminie wystarczy zapamiętać: struct = mała paczka danych kopiowana przez wartość, class = pełnoprawny obiekt kopiowany przez referencję.

Schemat

KLASA (typ referencyjny)                STRUCT (typ wartościowy)

zmienna a ──► [obiekt na stercie]       zmienna p1: (X=10, Y=20)  <- dane wprost
zmienna b ──►      ^ ten sam            zmienna p2 = p1;
              (a i b wskazują na        p2: (X=10, Y=20)  <- NIEZALEŻNA KOPIA
               ten sam obiekt)          zmiana p2.X = 99 NIE zmienia p1!

Przykład z życia

Typ Point (punkt x, y) w bibliotekach graficznych .NET jest strukturą — tworzysz ich tysiące podczas rysowania (każdy piksel, każdy wierzchołek), więc lekkość struktury i brak potrzeby "odśmiecania" pamięci przez garbage collector realnie przyspiesza program. Podobnie DateTime (data i godzina) oraz decimal (dokładne liczby, np. do kwot pieniężnych) w C# to struktury.

Kod (C#)

using System;

// Struktura opisująca punkt na płaszczyźnie.
struct Punkt
{
    public int X;
    public int Y;

    public Punkt(int x, int y)
    {
        X = x;
        Y = y;
    }

    public double OdlegloscOdZera()
    {
        return Math.Sqrt(X * X + Y * Y);
    }

    public override string ToString()
    {
        return $"({X}, {Y})";
    }
}

class Program
{
    // Metoda dostaje KOPIĘ struktury — zmiany wewnątrz nie wyjdą na zewnątrz.
    static void SprobujPrzesunac(Punkt p)
    {
        p.X += 100;
        p.Y += 100;
        Console.WriteLine($"Wewnątrz metody: {p}");
    }

    static void Main()
    {
        Punkt p1 = new Punkt(3, 4);
        Punkt p2 = p1; // kopiowana jest cała struktura, nie referencja

        p2.X = 99;

        Console.WriteLine($"p1 = {p1}");                       // (3, 4) - bez zmian!
        Console.WriteLine($"p2 = {p2}");                       // (99, 4)
        Console.WriteLine($"Odległość p1 od zera: {p1.OdlegloscOdZera():F2}");

        SprobujPrzesunac(p1);
        Console.WriteLine($"p1 po SprobujPrzesunac: {p1}");    // wciąż (3, 4)!
    }
}

Komentarz i wyjaśnienie kodu

Struktura Punkt ma dwa pola (X, Y), konstruktor ustawiający je od razu przy tworzeniu oraz dwie metody. Kluczowy moment jest w Main: Punkt p2 = p1; tworzy pełną, niezależną kopię — zmiana p2.X nie rusza p1.X. Gdyby Punkt był klasą, ta sama linijka sprawiłaby, że p1 i p2 wskazywałyby na ten sam obiekt, a zmiana jednej zmieniałaby też drugą. Ta sama zasada widać w SprobujPrzesunac — metoda dostaje kopię, więc zmiany w niej "znikają" po jej zakończeniu.

Ćwiczenie samodzielne

Zmień Punkt na klasę (usuń słowo struct, dodaj class) i uruchom ten sam kod z lekcji jeszcze raz. Zaobserwuj, jak zmienia się wynik — p1 teraz też się zmieni po modyfikacji p2. To najlepszy sposób, żeby poczuć różnicę na własne oczy.

Plac zabaw — wypróbuj online

Poniższy kod wykonuje się od razu w Twojej przeglądarce — nic nie trzeba instalować. Zmień kod i kliknij „Uruchom”.

CSHARP

Zadania do pracy własnej

  1. Utwórz strukturę Kolor z polami R, G, B (typu byte) oraz metodą ToString() zwracającą tekst w formacie "#RRGGBB" (w systemie szesnastkowym — użyj R.ToString("X2")).

  2. Utwórz strukturę ZakresDat z polami Od i Do (typu DateTime) oraz metodą Zawiera(DateTime data) zwracającą true, jeśli podana data mieści się w zakresie.

  3. Utwórz strukturę Ulamek reprezentującą ułamek zwykły (licznik/mianownik) z metodami Dodaj, Pomnoz i Skroc (skracającą ułamek przez największy wspólny dzielnik). Zwróć uwagę, żeby metody zwracały NOWY Ulamek, a nie modyfikowały istniejący — to zgodne z duchem typów wartościowych.

Typowe błędy

Oczekiwanie, że zmiana pola struktury przekazanej do metody "wyjdzie na zewnątrz" — bez słowa kluczowego ref metoda zawsze dostaje kopię struktury. To zaskakuje osoby przyzwyczajone do klas.

Tworzenie dużych, "ciężkich" struktur — jeśli struktura ma wiele pól albo duże dane, każde kopiowanie (a dzieje się to często i niejawnie) kosztuje pamięć i czas. Zasada praktyczna: struct dla małych paczek danych (kilka prostych pól), class dla wszystkiego większego.

Mylenie struct z klasą przy dziedziczeniu — struktury w C# NIE mogą dziedziczyć po innej strukturze ani klasie (mogą tylko implementować interfejsy). Jeśli potrzebujesz dziedziczenia, potrzebujesz klasy.

Nawiązanie do egzaminu zawodowego

Struktury realizują efekt INF.04.3.2 — "posługuje się złożonymi typami danych", konkretnie rekordami grupującymi kilka wartości. Zrozumienie różnicy między typem wartościowym a referencyjnym jest też fundamentem do świadomego programowania obiektowego z lekcji 10-16 tego działu.