Niemutowalność z init i with
Używaj setterów dostępnych tylko podczas inicjalizacji do tworzenia niemutowalnych obiektów oraz wyrażeń with do tworzenia kopii modyfikowanych niedestrukcyjnie.
Niemutowalność z init i with to bezpłatna lekcja C# Academy na CoddyKit. To lekcja 2 z 4. Możesz przeczytać całą lekcję poniżej za darmo — a potem ćwiczyć ją interaktywnie w przeglądarce z wbudowanym edytorem kodu i tutorem AI dostępnym 24/7. To część ścieżki edukacyjnej C# Academy, a Twój postęp synchronizuje się między webem a aplikacją CoddyKit. Kurs C# Academy zawiera 4 lekcji w sumie.
Czym jest niezmienność
Obiekt niezmienny nie może zostać zmodyfikowany po utworzeniu. Niezmienność zapobiega przypadkowym modyfikacjom, domyślnie zapewnia bezpieczeństwo wątkowe obiektów i ułatwia analizowanie kodu. C# udostępnia settery init oraz wyrażenia with, dzięki którym tworzenie niezmiennych obiektów jest wygodne.
Settery tylko init
Akcesor init umożliwia ustawienie właściwości podczas inicjalizacji obiektu (w konstruktorze lub inicjalizatorze obiektu), ale nigdy później. Jest niezmiennym odpowiednikiem akcesora set.
public class Point
{
public double X { get; init; }
public double Y { get; init; }
}
var p = new Point { X = 3.0, Y = 4.0 }; // OK — init phase
p.X = 5.0; // COMPILE ERROR — cannot set after init
// All of these are valid init-phase assignments:
var p2 = new Point(X: 1.0, Y: 2.0);
var p3 = new Point { X = 0, Y = 0 };init we właściwościach pozycyjnych rekordów
Właściwości pozycyjne rekordów są domyślnie dostępne tylko z użyciem init — właśnie to zapewnia niezmienność rekordów. Kompilator generuje dla każdego parametru pozycyjnego zapis get; init;.
// This record:
public record Person(string Name, int Age);
// Is equivalent to:
public record Person
{
public string Name { get; init; }
public int Age { get; init; }
public Person(string Name, int Age) { this.Name = Name; this.Age = Age; }
// + Deconstruct, Equals, GetHashCode, ToString
}
var alice = new Person("Alice", 30);
alice.Name = "Bob"; // COMPILE ERRORWyrażenia with: niedestrukcyjna modyfikacja
Wyrażenie with tworzy kopię rekordu ze zmienionymi określonymi właściwościami. Oryginał pozostaje niezmieniony — to prawdziwa niezmienność.
public record Person(string Name, int Age, string Email);
var alice = new Person("Alice", 30, "alice@example.com");
// Create a modified copy — alice is unchanged
var olderAlice = alice with { Age = 31 };
var renamed = alice with { Name = "Alicia", Email = "alicia@example.com" };
Console.WriteLine(alice.Name); // Alice (unchanged)
Console.WriteLine(olderAlice.Age); // 31
Console.WriteLine(renamed.Name); // Aliciawith dla typów innych niż rekordy
C# 10 umożliwia używanie with dla dowolnej struktury lub klasy, która obsługuje semantykę konstruktora kopiującego, ale najnaturalniej korzysta się z tego rozwiązania w przypadku rekordów. W klasach trzeba zaimplementować je ręcznie.
// struct with with-expression:
public struct Temperature
{
public double Celsius { get; init; }
public double Fahrenheit => Celsius * 9 / 5 + 32;
}
var t1 = new Temperature { Celsius = 20 };
var t2 = t1 with { Celsius = 25 }; // copy with change
Console.WriteLine(t1.Celsius); // 20 — unchanged
Console.WriteLine(t2.Celsius); // 25Łańcuchowanie wyrażeń with
Można łączyć wiele wyrażeń with, aby tworzyć złożone transformacje, w których każdy krok tworzy nową niezmienną wartość na podstawie poprzedniej.
public record Order(int Id, string Status, decimal Total, DateTime UpdatedAt);
var order = new Order(42, "Pending", 99.99m, DateTime.UtcNow);
// Apply a promotion discount then mark as confirmed
var finalOrder = order
with { Total = order.Total * 0.9m } // 10% off
with { Status = "Confirmed" }
with { UpdatedAt = DateTime.UtcNow };
Console.WriteLine(order.Status); // Pending (original unchanged)
Console.WriteLine(finalOrder.Status); // ConfirmedWłaściwości obliczane w typach niezmiennych
Właściwości pochodne w typach niezmiennych są z natury czyste — obliczają wynik na podstawie niezmiennych wartości właściwości i zawsze zwracają ten sam wynik dla tych samych danych wejściowych.
public record Money(decimal Amount, string Currency)
{
public Money Add(Money other)
{
if (Currency != other.Currency)
throw new InvalidOperationException("Currency mismatch");
return this with { Amount = Amount + other.Amount };
}
public Money Multiply(decimal factor) =>
this with { Amount = Amount * factor };
public override string ToString() =>
$"{Amount:F2} {Currency}";
}
var price = new Money(10.00m, "USD");
var tax = price.Multiply(0.08m);
var total = price.Add(tax);
Console.WriteLine(total); // 10.80 USDNiezmienne kolekcje
Rekordy można łączyć z ImmutableList<T> i innymi typami z System.Collections.Immutable, aby tworzyć w pełni niezmienne struktury danych.
using System.Collections.Immutable;
public record ShoppingCart(
string UserId,
ImmutableList<CartItem> Items)
{
public ShoppingCart AddItem(CartItem item) =>
this with { Items = Items.Add(item) };
public ShoppingCart RemoveItem(int productId) =>
this with { Items = Items.RemoveAll(i => i.ProductId == productId) };
public decimal Total => Items.Sum(i => i.Price * i.Quantity);
}Bezpieczeństwo wątkowe dzięki niezmienności
Niezmienne obiekty są z natury bezpieczne wątkowo — podczas udostępniania ich między wątkami nie jest potrzebna synchronizacja, ponieważ po utworzeniu ich stan nie może się zmienić.
// Immutable config record shared across all threads safely
public record AppConfig(
string ConnectionString,
int MaxRetries,
TimeSpan Timeout);
// Register as singleton — safe because record is immutable
builder.Services.AddSingleton(
new AppConfig(
ConnectionString: config["DB"]!,
MaxRetries: 3,
Timeout: TimeSpan.FromSeconds(30)));
// Any thread can read this simultaneously without locksPraktyczny przykład: funkcyjne pozyskiwanie zdarzeń
Niezmienne rekordy naturalnie pasują do pozyskiwania zdarzeń: każde zdarzenie domenowe jest niezmienne, a stan jest wyprowadzany przez składanie zdarzeń — bez modyfikacji i niespodzianek.
public record OrderState(
int Id,
string Status = "Draft",
decimal Total = 0m);
public static OrderState Apply(OrderState state, object evt) => evt switch
{
OrderPlaced e => state with { Status = "Pending", Total = e.Total },
OrderShipped _ => state with { Status = "Shipped" },
OrderCancelled _ => state with { Status = "Cancelled" },
_ => state
};
// Fold events to get current state:
var state = events.Aggregate(
new OrderState(id),
Apply);Szybkie sprawdzenie
Co wyrażenie 'with' robi z oryginalnym rekordem?
Podsumowanie: Niezmienność z init & with
Najważniejsze informacje:
- Akcesor
init: właściwość można ustawić tylko podczas inicjalizacji, nigdy później - Właściwości pozycyjne rekordów są domyślnie dostępne tylko z użyciem
init - Wyrażenie
with: tworzy zmodyfikowaną kopię — oryginał pozostaje niezmieniony - Łączenie wyrażeń
withumożliwia wykonywanie wieloetapowych transformacji - Typy niezmienne są bezpieczne wątkowo bez synchronizacji
- Połączenie z
ImmutableList<T>pozwala tworzyć w pełni niezmienne grafy obiektów
Często zadawane pytania
Czy lekcja „Niemutowalność z init i with” jest bezpłatna?
Tak — pełny tekst „Niemutowalność z init i with” jest dostępny za darmo tutaj w sieci. Aby ćwiczyć ją interaktywnie (wbudowany edytor kodu i tutor AI dostępny 24/7) i odblokować resztę kursu C# Academy, przejdź na CoddyKit PRO. Kurs C# Academy zawiera 4 lekcji w sumie.
Co nauczysz się w „Niemutowalność z init i with”?
Używaj setterów dostępnych tylko podczas inicjalizacji do tworzenia niemutowalnych obiektów oraz wyrażeń with do tworzenia kopii modyfikowanych niedestrukcyjnie. Ćwiczysz C# Academy z praktycznym kodem, który uruchamiasz bezpośrednio w przeglądarce, a tutor AI dostępny 24/7 odpowiada na Twoje pytania podczas pracy nad lekcją.
Czy potrzebuję doświadczenia, aby zacząć C# Academy?
Nie wymagamy żadnego doświadczenia. C# Academy w CoddyKit jest strukturyzowany dla początkujących i zaawansowanych użytkowników, więc możesz zacząć tutaj lub od początku i uczyć się w swoim tempie. To lekcja 2 z 4.
Ile czasu zajmuje lekcja „Niemutowalność z init i with”?
Większość lekcji CoddyKit trwa około 5–10 minut. Każda lekcja to mały, interaktywny krok, dzięki czemu robisz systematyczne postępy i zawsze wracasz dokładnie do tego samego miejsca — na webie i w aplikacji.
Czy mogę pisać i uruchamiać kod w tej lekcji C# Academy?
Tak. Każda lekcja C# Academy zawiera wbudowany edytor kodu, więc piszesz i uruchamiasz prawdziwy kod bezpośrednio w przeglądarce i od razu otrzymujesz sprzężenie zwrotne od AI — bez konfiguracji na komputerze.
Wszystkie lekcje w tym kursie
- Typy record: podstawy i składnia
- Niemutowalność z init i with
- Równość wartości i dekonstrukcja
- Rekordy w projektowaniu sterowanym domeną