C# Records i wyrażenie with – niemutowalny kod bez boilerplate’u

C# Records i wyrażenie with – niemutowalny kod bez boilerplate’u

Mutowalne klasy w C# to jeden z najczęstszych powodów trudnych do wykrycia bugów, nieczytelnego kodu i kosztownych refactorów. Jeśli kiedykolwiek kopiowałeś cały obiekt tylko po to, by zmienić jedną właściwość, ten artykuł jest dla Ciebie.

Pokażę Ci, jak records, wyrażenie with oraz typy anonimowe pozwalają pisać niemutowalny, odporny na zmiany kod C#, bez ręcznego kopiowania i bez magii.


Dlaczego mutowalne klasy w C# są problemem?

Problem: mutacja stanu w niekontrolowanym miejscu

Spójrz na klasyczny przykład:

public class Customer
{
    public string Name { get; set; } = string.Empty;
    public string Address { get; set; } = string.Empty;
    public string Email { get; set; } = string.Empty;
}

var customer = new Customer
{
    Name = "Marcin",
    Address = "Katowice",
    Email = "marcin@dev-hobby.pl"
};

// 50 linii dalej...
customer.Name = "Tomek";

👉 Co tu poszło nie tak?

  • Każdy może zmienić każdą właściwość
  • Nie wiesz: kto, kiedy i dlaczego
  • Debugowanie takiego kodu to strata czasu

W małym projekcie jeszcze da się z tym żyć. W większym – to recepta na dług techniczny.


Próba ratunku: klasy niemutowalne (init)

Problem: ręczne kopiowanie właściwości

public class CustomerImmutable
{
    public string Name { get; init; } = string.Empty;
    public string Address { get; init; } = string.Empty;
    public string Email { get; init; } = string.Empty;
}

var updated = new CustomerImmutable
{
    Name = "Tomek",
    Address = customer.Address,
    Email = customer.Email
};

To działa, ale…

❌ dużo boilerplate’u
❌ słaba skalowalność
❌ każda nowa właściwość = kolejne kopiowanie


C# Records – czym są i dlaczego rozwiązują problem?

Czym jest record w C#?

record to typ wprowadzony w C# 9, zaprojektowany do pracy z danymi, a nie z cyklem życia obiektu.

public record CustomerRecord(string Name, string Address, string Email);

Co dostajesz „za darmo”?

  • niemutowalność (init-only)
  • porównywanie po wartości, nie po referencji
  • automatyczne Equals, GetHashCode, ToString
  • wbudowane kopiowanie obiektu

Wyrażenie with – sedno niedestrukcyjnej mutacji

Jak działa with?

var marcin = new CustomerRecord("Marcin", "Katowice", "marcin@dev-hobby.pl");
var tomek = marcin with { Name = "Tomek" };

✅ tworzony jest nowy obiekt
✅ oryginał pozostaje niezmieniony
✅ kopiowane są wszystkie właściwości automatycznie

Dlaczego to jest lepsze?

  • zmieniasz tylko to, co ma się zmienić
  • kod jest odporny na rozbudowę
  • brak ręcznego kopiowania

Typy anonimowe + with – mało znany game-changer

Od C# 9 wyrażenie with działa także na typach anonimowych, o ile wszystkie właściwości są readonly.

var person = new
{
    Name = "Robert",
    City = "Warszawa"
};
var updated = person with { Name = "Jacek" };

👉 Tak, to naprawdę działa.


Realny case: LINQ + obliczenia in-memory

Problem: kopiowanie projekcji

var orders = context.Orders
    .Select(o => new
    {
        o.OrderId,
        o.CustomerName,
        o.TotalAmount,
        DiscountAmount = 0m
    })
    .ToList();

var ordersWithDiscount = orders
    .Select(o => new
    {
        o.OrderId,
        o.CustomerName,
        o.TotalAmount,
        DiscountAmount = CalculateDiscount(o.TotalAmount)
    });

❌ nowy typ
❌ ręczne kopiowanie
❌ słaba utrzymywalność


Rozwiązanie: with w LINQ

var ordersWithDiscount = orders
    .Select(o => o with
    {
        DiscountAmount = CalculateDiscount(o.TotalAmount)
    })
    .ToList();

✔ czytelnie
✔ bez boilerplate’u
✔ odporne na zmiany


Kiedy używać records, a kiedy NIE?

✔ Stosuj records gdy:

  • tworzysz DTO
  • pracujesz z projekcjami LINQ
  • modelujesz value objects (DDD)
  • obsługujesz eventy (event sourcing)

❌ Nie stosuj records gdy:

  • obiekt ma złożony cykl życia
  • stan zmienia się często i celowo
  • pracujesz z encjami domenowymi EF Core

Checklist: czy record + with ma tu sens?

  • zmieniam 1–2 właściwości
  • reszta danych powinna pozostać bez zmian
  • zależy mi na niemutowalności
  • kod ma być odporny na refactor

Jeśli zaznaczyłeś 3+ punkty → użyj record.


Zobacz też


Podsumowanie

with to nie jest syntaktyczny bajer.
To narzędzie, które:

  • zmniejsza liczbę bugów
  • upraszcza kod
  • eliminuje boilerplate
  • promuje niemutowalność

Jeśli nadal wszędzie używasz klas z setterami — to nie jest prostota. To dług techniczny.


👉 Zostaw komentarz: gdzie w Twoim kodzie with zrobiłby największą różnicę?

Dodaj komentarz

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *