CodeGym /Kursy /C# SELF /record z jawnie zdefi...

record z jawnie zdefiniowanym ciałem i record struct

C# SELF
Poziom 19 , Lekcja 3
Dostępny

1. Wprowadzenie

Pozycjonująca składnia record-klasy jest naprawdę wygodna w prostych przypadkach:

public record User(string Name, int Age);
Pozycjonująca record-klasa

Ale czasem masz ogromną ochotę dodać do record dodatkowe metody, niestandardowe właściwości, zmienić modyfikatory dostępu, dorzucić logikę do konstruktora (np. walidację albo "automatyczną" konwersję danych). Niestety, w pozycjonującej deklaracji nie ma gdzie tego wrzucić! Czas przejść do jawnej składni ciała, jeśli:

  • Musisz rozszerzyć record o metody, właściwości lub dodatkową logikę.
  • Chcesz kontrolować zachowanie właściwości (settery, gettery, inits, walidacja).
  • Potrzebujesz kilku różnych konstruktorów do różnych sposobów inicjalizacji.
  • Chcesz dodać interfejsy lub zaimplementować specjalne metody.

Budujemy record z ciałem

Składnia jest bardzo podobna do klasy. Kojarzysz taki zapis:


public class User
{
    /* ... */
}

Tylko teraz to jest record:


public record User
{
    // Jawnie zdefiniowane właściwości
    public string Name { get; init; }
    public int Age { get; init; }

    // Dodatkowa logika
    public string GetGreeting()
    {
        return $"Cześć, mam na imię {Name} i mam {Age} lat!";
    }

    // Własny konstruktor
    public User(string name, int age)
    {
        Name = name;
        if (age < 0)
            throw new ArgumentException("Wiek nie może być ujemny!");
        Age = age;
    }
}

Ciekawostka: Jeśli jawnie definiujesz swoje właściwości, kompilator nie tworzy automatycznie właściwości z pozycjonującej składni. Wszystko jawnie, wszystko pod kontrolą.

Wersja mieszana: hybrydowa składnia

C# pozwala połączyć oba światy: możesz zadeklarować pozycjonujący record i dodać mu ciało:


public record User(string Name, int Age)
{
    public string GetGreeting()
    {
        return $"Jestem {Name}, mam {Age} lat!";
    }
}
Pozycjonujący record z ciałem i dodatkowymi metodami

W tym przypadku właściwości Name i Age są nadal automatycznie tworzone przez pozycjonującą składnię, a twoje dodatkowe metody wygodnie siedzą w ciele.

2. Różnice względem zwykłych record — niuanse ciała

  • Jawny record pozwala w pełni kontrolować konstruktory, właściwości, metody.
  • Możesz zaimplementować interfejsy albo dodać własną logikę porównywania, jeśli masz złożone zasady identyfikacji obiektu.
  • W przeciwieństwie do prostego record z pozycjonującą składnią, żeby dodać nowe właściwości wystarczy je zadeklarować w ciele recorda.

// Błąd początkujących!
public record User(string Name, int Age)
{
    public string Name { get; init; } // ← konflikt! Powtórna deklaracja właściwości
}

Błędy początkujących: Niektórzy studenci próbują jednocześnie pisać public record User(string Name, int Age) i dodawać do ciała właściwość public string Name { get; init; }, myśląc, że to dwie różne zmienne. Nie! To będzie konflikt (powtórna deklaracja). Albo używasz w pełni pozycjonującej składni, albo jawnie definiujesz właściwości — nie mieszaj.

Praktyczne przykłady

Kontynuujemy pisanie aplikacji konsolowej, w której użytkownik tworzy zamówienia. Załóżmy, że mamy klasę zamówienia Order, która do tej pory wyglądała tak:

public record Order(string Product, int Quantity, double Price);

Załóżmy, że teraz potrzebujemy walidacji ilości (quantity nie może być mniejsze niż 1) i dodatkowej właściwości — licznika całkowitego kosztu:


public record Order
{
    public string Product { get; init; }
    public int Quantity { get; init; }
    public double Price { get; init; }
    public double TotalCost => Quantity * Price;

    public Order(string product, int quantity, double price)
    {
        Product = product ?? throw new ArgumentNullException(nameof(product));
        if (quantity < 1)
            throw new ArgumentException("Ilość musi być co najmniej 1!");
        Quantity = quantity;
        Price = price;
    }

    public override string ToString()
        => $"Produkt: {Product}, Ilość: {Quantity}, Suma: {TotalCost}";
}

Zwróć uwagę, że jawnie zdefiniowaliśmy właściwości, zrobiliśmy je z init-setterem (obiekty niezmienne), dodaliśmy automatyczne liczenie kosztu — kod jest bardziej elastyczny!

Wywołanie w programie:

var order = new Order("Rower", 2, 15000);
Console.WriteLine(order); // Produkt: Rower, Ilość: 2, Suma: 30000

3. record struct

Ewolucja struct-ów

Przed pojawieniem się record struct struktury w C# były "prostymi wołami roboczymi" — szybko kopiowane, trzymane na stosie, świetne do krótkich “Parcel-i” danych (np. współrzędnych albo kolorów). Ale nie miały wszystkich bajerów records: nie było pozycjonującej składni, with-wyrażeń, porównywania po wartości domyślnie i innych “słodkich bułeczek”.

Teraz w C# można deklarować struktury w stylu record:

public record struct Point(int X, int Y);
Pozycjonująca składnia record struct

Co to w ogóle daje?

  • Automatyczna implementacja Equals, GetHashCode, ToString — twoja struktura umie się ładnie porównywać i drukować!
  • Składnia with-klonowania: var p2 = p1 with { X = 10 };
  • Możliwość użycia pozycjonującej lub jawnej składni.

Porównanie: klasyczny struct VS record struct

struct record struct
Pozycjonująca składnia Nie Tak
with-wyrażenie Nie Tak
Niezmienność Nie (domyślnie) Tak (init)
Porównanie po wartościach Nie (domyślnie) Tak
ToString Standardowy Ładny

Jawna składnia ciała dla record struct

Wszystko tak samo jak w zwykłym record, tylko struct:


public record struct Rectangle
{
    public int Width { get; init; }
    public int Height { get; init; }

    public int Area => Width * Height;

    public Rectangle(int width, int height)
    {
        Width = width > 0 ? width : throw new ArgumentException("Szerokość > 0");
        Height = height > 0 ? height : throw new ArgumentException("Wysokość > 0");
    }

    public void Print()
    {
        Console.WriteLine($"Wymiary: {Width} x {Height}, pole: {Area}");
    }
}

Przykład użycia:

var rect = new Rectangle(10, 7);
rect.Print(); // Wymiary: 10 x 7, pole: 70

// Klonujemy i zmieniamy szerokość, nie ruszając oryginału
var wideRect = rect with { Width = 20 };
wideRect.Print(); // Wymiary: 20 x 7, pole: 140

Specyfika record struct

  • To wciąż struct — value type. Kopiuje się przy przypisaniu!
  • Wszystkie plusy records: porównanie po wartościach, with-klonowanie, ładny ToString.
  • Zalecane do małych, kompaktowych niezmiennych zbiorów danych, gdzie ważne jest unikanie alokacji na stercie.
  • Możesz zadeklarować pozycjonujące parametry albo jawnie “rozwinąć” ciało.

4. Typowe błędy i pułapki

Nadmierna mutowalność: record struct nie robi pól automatycznie niezmiennymi, jeśli zadeklarujesz je jako zwykłe (np. public int Value;). Używaj init-setterów dla truly immutable struct!

Porównywanie: Jeśli dodajesz nowe pola ręcznie (nie w pozycjonującej składni), pamiętaj: tylko te pola, które są w konstruktorze lub mają init-setter, biorą udział w auto-porównywaniu po wartości.

Kopiowanie: To struct, więc... wszystko się kopiuje! Nie myl z referencyjnymi recordami.

Zamieszanie z with-wyrażeniami: One zawsze robią shallow copy, czyli NIE robią głębokiego kopiowania zagnieżdżonych obiektów.

Komentarze
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION