Struktury — małe wartości, które kopiujemy
Pozycja pionka składa się z dwóch współrzędnych. Gdy kopiujesz pozycję, zwykle chcesz niezależną wartość, a nie drugą nazwę tego samego zmiennego obiektu.
Czego się dziś nauczysz
Po lekcji wyjaśnisz poznany mechanizm, prześledzisz działanie programu i zastosujesz go w samodzielnym zadaniu. Pracuj po kolei według spisu OOP. Potrzebujesz podstaw zmiennych, warunków, metod i wcześniejszych tematów tego modułu.
Zaplanuj 1–2 godziny lekcyjne, a zadania rozszerzające wykonaj w osobnym laboratorium. Używamy .NET 8 lub nowszego. Nie łącz kilku kompletnych przykładów w jednym Program.cs.
Wyjaśnienie krok po kroku
Struct opisuje typ wartościowy
Struktura może mieć pola, właściwości, metody i konstruktory. Przy zwykłym przypisaniu kopiujemy jej wartość. To istotna różnica wobec klasy, gdzie kopiowana jest referencja. Nie wybieramy struct tylko dlatego, że chcemy napisać mniej kodu albo „oszczędzić stos”.
Tak wygląda to w kodzie — Definicja typu — poza Main. To fragment ilustracyjny, nie cały Program.cs.
struct Rozmiar
{
public int Szerokosc;
public int Wysokosc;
}
Rozmiar grupuje dwie liczby w jedną wartość. Każde przypisanie kopiuje te pola.
Przed i po zmianie typu
Dla klasy z właściwością X zapis b = a i potem b.X = 9 zmienia obiekt widoczny przez a. Dla struktury z taką właściwością b = a kopiuje wartość i zmiana b nie zmienia a. Wyjątkiem od niezależności zawartości jest współdzielony obiekt wskazywany przez pole referencyjne wewnątrz struktury.
Wartość domyślna ma znaczenie
default(Punkt) ustawia pola struktury na wartości domyślne, np. współrzędne na 0.
Elementy nowej tablicy struktur także zaczynają jako wartości domyślne. Projektuj typ tak, aby
rozumieć tę sytuację. Sam konstruktor z walidacją nie blokuje powstania default.
Tak wygląda to w kodzie — Fragment wnętrza Main; Punkt ma publiczne pola X i Y. To fragment ilustracyjny, nie cały Program.cs.
Punkt zero = default(Punkt);
Console.WriteLine(zero.X); // 0
Console.WriteLine(zero.Y); // 0
Default tworzy wartość z domyślnymi polami. WriteLine(int) wypisuje liczbę i kończy wiersz.
Konstruktory struktur
Możesz deklarować konstruktor z parametrami. Od C# 10 można także zdefiniować publiczny konstruktor bezparametrowy. Jawne new S() wywoła taki konstruktor, lecz default(S) i zerowanie elementów tablicy nie wykonają go. Nie przenoś z klasy reguły „własny konstruktor usuwa możliwość new bez argumentów” na strukturę.
Tak wygląda to w kodzie — Definicja typu — poza Main. To fragment ilustracyjny, nie cały Program.cs.
struct Ustawienia
{
public int Proby;
public Ustawienia() { Proby = 3; }
}
// new Ustawienia().Proby daje 3
// default(Ustawienia).Proby daje 0
Publiczny konstruktor bezparametrowy działa w C# 10 i nowszym. Default nie uruchamia tego konstruktora.
Readonly struct
Readonly struct pomaga projektować wartości niezmienne: pola instancji są readonly, a właściwości instancji nie mogą mieć zwykłych setterów zmieniających stan. Zamiast przesuwać istniejącą pozycję, metoda może zwrócić nową pozycję. To ułatwia śledzenie zmian. Nadal nie zamraża obiektów wskazywanych przez ewentualne pola referencyjne.
Tak wygląda to w kodzie — Definicja typu — poza Main. To fragment ilustracyjny, nie cały Program.cs.
readonly struct Punkt
{
public int X { get; }
public Punkt(int x) { X = x; }
public Punkt Wprawo() { return new Punkt(X + 1); }
}
Wprawo zwraca nową wartość; nie zmienia bieżącej. Aby zachować wynik, klient przypisuje go do zmiennej.
Dziedziczenie, null i boxing
Struktura nie dziedziczy po wybranej klasie ani innej strukturze, ale może implementować interfejsy. Zwykły Punkt nie przechowuje null; Punkt? oznacza wartość opcjonalną. Przypisanie struktury do object lub zmiennej interfejsowej może wykonać boxing: opakować kopię wartości w obiekt. Na podstawowym poziomie nie potrzebujesz tego mechanizmu do przechowywania współrzędnych.
Uwaga na kopie w kolekcjach
Pobranie struktury z List<Punkt> daje wartość. Zmiana lokalnej kopii nie zapisze jej automatycznie na liście. Dla mutowalnych struktur konieczne bywa przypisanie zmienionej wartości z powrotem. Niezmienne małe struktury pozwalają wyraźnie zobaczyć tę operację.
Przewidź → uruchom → zmień
Kompletny, osobny plik Program.cs. Przed uruchomieniem przewidź wynik. Każdy program
uruchamiaj w oddzielnym projekcie konsolowym.
using System;
readonly struct Punkt
{
public int X { get; }
public int Y { get; }
public Punkt(int x, int y) { X = x; Y = y; }
public Punkt Wprawo() { return new Punkt(X + 1, Y); }
}
class Program
{
static void Main()
{
Punkt a = new Punkt(2, 3);
Punkt b = a;
b = b.Wprawo();
Punkt zero = default(Punkt);
Console.WriteLine($"a: {a.X}, {a.Y}");
Console.WriteLine($"b: {b.X}, {b.Y}");
Console.WriteLine($"default: {zero.X}, {zero.Y}");
}
}
a: 2, 3
b: 3, 3
default: 0, 0
Co dokładnie robi kod?
B otrzymuje kopię wartości a. Wprawo nie zmienia tej wartości: tworzy i zwraca nowy Punkt. Przypisanie zapisuje wynik w b, a pozostaje bez zmian. Konstruktor ustawia automatyczne właściwości get-only. Default jest operatorem języka, nie metodą biblioteki. Console.WriteLine wypisuje tekst z interpolacji. Przykład zakłada małe współrzędne, bez przepełnienia int.
Zmień jedną daną i zapisz przewidywany wynik przed uruchomieniem. Następnie porównaj obserwację z przewidywaniem i wskaż instrukcję odpowiedzialną za różnicę.
Rekord to struktura, a unia?
Typ rekordowy z podstawy programowej to w C# właśnie struct — albo record w
nowszym zapisie. Unii — typu, w którym wszystkie pola dzielą to samo miejsce w
pamięci i naraz sensowne jest tylko jedno — C# nie ma jako osobnej konstrukcji. Da się ją odtworzyć
atrybutem [StructLayout(LayoutKind.Explicit)] z jawnymi przesunięciami pól, ale robi
się to wyłącznie przy współpracy z kodem systemowym. W zwykłym kodzie ten sam problem rozwiązuje się
dziedziczeniem albo typem z polem opisującym wariant.
Zadania
Nie ma tu gotowych rozwiązań. Oddaj kod, własne przypadki testowe oraz krótkie wyjaśnienie decyzji. Pierwsze zadanie jest podstawowe; ostatnie pozwalają pogłębić temat.
Zaprojektuj readonly struct Rozmiar z szerokością i wysokością oraz właściwością Pole. Zdecyduj, co oznacza default i opisz tę decyzję.
W dwóch osobnych programach utwórz mutowalny typ z X: raz class, raz struct. Wykonaj a → b, zmień b.X i porównaj odczyt a.X.
Zbuduj strukturę zawierającą List<int>. Skopiuj ją, dopisz liczbę do listy kopii i wyjaśnij obserwację. Add(int) dopisuje element i zwraca void.
Rozszerzenie: porównaj new i default dla struktury z jawnym konstruktorem bezparametrowym, który ustawia pole na 5. Sprawdź również element nowej tablicy.
Sprawdź, czy rozumiesz
- Co jest kopiowane przy przypisaniu struct?
- Czy readonly zamraża obiekt przechowywany w polu?
- Dlaczego default może ominąć twój konstruktor?
Dokumentacja: Microsoft Learn — Struktury.