JavaRush /Курси /C# SELF /Незмінність і with-ви...

Незмінність і with-вирази

C# SELF
Рівень 19 , Лекція 2
Відкрита

1. Що таке незмінність?

Почнімо з аналогії: уявіть, що ви — бухгалтер, який щодня формує звіт про продажі. Як справжній профі, ви не змінюєте звіт минулого тижня, а створюєте новий — на основі попереднього, але з оновленими даними. Так само і з незмінними об’єктами у програмуванні: після створення такий об’єкт більше не змінюється, а будь-які «зміни» означають створення нової копії.

Незмінність (immutability) — це властивість об’єкта не змінюватися після ініціалізації. Усі його властивості стають «замороженими»: якщо хочете інше значення — створіть новий об’єкт.

Навіщо це потрібно?

  • Забезпечує безпеку: якщо ваш об’єкт ніхто не може випадково змінити, він не зіпсує ваші дані. Таке часто трапляється у багатопотокових програмах, коли кілька потоків намагаються щось змінювати одночасно.
  • Спрощує налагодження: якщо об’єкт не змінюється, ви точно знаєте, що відбувалося після його створення.
  • Зручно передавати дані, особливо у розподілених системах, де копії можуть розходитися.
  • Дозволяє робити «знімки стану» (snapshots) — історія змін стає очевидною.

2. Незмінність типів record

Коли ви оголошуєте звичайний class, його властивості за замовчуванням змінні (mutable). Приклад:

public class UserProfile
{
    public string Name { get; set; }
    public int Age { get; set; }
}

var user = new UserProfile { Name = "Іван", Age = 25 };
user.Age = 26; // Усе гаразд — звичайний клас: змінюємо вік "на льоту"

У типів record ситуація інша: їх призначено для зберігання незмінних даних.


public record UserProfile(string Name, int Age);

// Створюємо об’єкт:
var user = new UserProfile("Іван", 25);

// Пробуємо змінити вік:
user.Age = 26; // Помилка компіляції: властивість лише для читання!
record — властивості лише для читання (init-only)

Властивості позиційного record оголошуються лише для читання (init-only). Ви не можете змінити їх після створення, але можете використати with-вираз, щоб створити нову копію зі зміненою властивістю.

Змінні класи vs незмінні record

Клас Позиційний record
Властивості за замовчуванням
get; set;
Незмінні
get; init;
Як змінити
Звернення до властивості
Лише створення нової копії
Порівняння об’єктів
За посиланням (ReferenceEquals)
За значенням (Equals)
Зручно для передавання даних
Не завжди
Так

3. with-вирази

Можливо, ви подумали: «record — це круто, але як тепер жити, якщо їх не можна змінювати?» Ось тут і з’являється магія with-виразів!

with — це спеціальний синтаксис, який дозволяє створити нову копію record, змінюючи лише потрібні властивості.
Тобто: «Взяти цей об’єкт, зробити його копію, але тут підправити кілька властивостей.»

Найпростіший приклад


var user1 = new UserProfile("Анна", 30);
// ... але життя не стоїть на місці, і Анна стала старшою
var user2 = user1 with { Age = 31 };
// user1 без змін, user2 — копія, але на рік старша

Console.WriteLine(user1); // UserProfile { Name = Анна, Age = 30 }
Console.WriteLine(user2); // UserProfile { Name = Анна, Age = 31 }

Під капотом

Це не мутант-клон, а новий об’єкт, створений за допомогою спеціального автогенерованого методу Clone(), який створює копію і підставляє нові значення.

Якби with-вирази були у житті, ви могли б прокидатися не в «старому втомленому тілі», а в копії себе з налаштованим настроєм і більшими м’язами (але тільки якби ви були record).

4. Трохи про вкладеність і копіювання

Якщо record містить інші record — усе гаразд:

public record Address(string City, string Street);
public record Student(string Name, int Age, string Email, Address Home);

var a1 = new Address("Київ", "Хрещатик");
var s1 = new Student("Олена", 21, "olena@mail.ua", a1);

var s2 = s1 with { Home = a1 with { Street = "Січових Стрільців" } };

Тут усе працюватиме по-справжньому незмінно, адже вкладений Address — теж record.

5. Останні нюанси

Позиційні record = компактність

Record можна оголошувати у «короткій» формі (позиційний синтаксис). Тоді всі властивості автоматично отримують init-only.

public record Course(string Name, int Credits);

var c1 = new Course("C#", 5);
var c2 = c1 with { Credits = 6 };

Аналогія з властивостями лише для читання (init-only)

У record можна явно оголошувати властивості так:

public record Student
{
    public string Name { get; init; }
    public int Age { get; init; }
}

Такі властивості теж можна змінювати лише під час ініціалізації (або за допомогою with).

6. Практика: демо-застосунок

Напишімо нашу навчальну «онлайн-школу». Припустімо, у нас уже є record для студента:

public record Student(string Name, int Age, string Email);

Класика: хтось помилився в адресі електронної пошти, а студент уже створив обліковий запис. Як «оновити» email? Звісно, за допомогою with!


var student = new Student("Катерина", 19, "kate@school.com");
var updatedStudent = student with { Email = "kateryna@school.com" };

// Перевіримо об’єкти:
Console.WriteLine(student);       // Student { Name = Катерина, Age = 19, Email = kate@school.com }
Console.WriteLine(updatedStudent); // Student { Name = Катерина, Age = 19, Email = kateryna@school.com }

7. Типові помилки та підводні камені

Тепер трохи про те, де студенти найчастіше помиляються під час роботи з незмінними record.

  • По-перше, чимало хто вважає, що with змінює вихідний об’єкт. Насправді вихідний об’єкт не змінюється; новий створюється зі зміненими полями. Через це легко потрапити в пастку й втратити нові значення.
  • По-друге, пам’ятайте: якщо всередині record є вкладені змінні об’єкти (наприклад, масив або List), то with-вираз не виконує глибокого копіювання! Ваша колекція залишиться спільною для обох копій.

public record Student(string Name, int Age, List<string> Subjects);

var s1 = new Student("Олег", 22, new List<string> { "Математика", "Фізика" });
var s2 = s1 with { };

s1.Subjects.Add("C#"); // Ого, тепер і s2.Subjects містить "C#"

Ось чому для справді незмінного стану краще використовувати лише прості типи або колекції, які самі по собі незмінні (ImmutableList<T> та інші з System.Collections.Immutable).

Якщо ви хочете гарантувати справжню незмінність, використовуйте ці колекції або виконуйте ручне глибоке копіювання.

Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ