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ż
- Zobacz też: Tworzenie klas w C#
- Zobacz też: LINQ – projekcje i wydajność
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ę?

