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 неизменяемые records

Класс 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, "lena@mail.ru", 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 = "ekaterina@school.com" };

// Проверим объекты:
Console.WriteLine(student);       // Student { Name = Екатерина, Age = 19, Email = kate@school.com }
Console.WriteLine(updatedStudent); // Student { Name = Екатерина, Age = 19, Email = ekaterina@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> { "Math", "Physics" });
var s2 = s1 with { };

s1.Subjects.Add("C#"); // Опа, теперь и s2.Subjects включает "C#"

Вот почему для truly immutable state лучше использовать только простые типы или коллекции, которые сами по себе неизменяемы (ImmutableList<T> и др. из System.Collections.Immutable).

Если вы хотите гарантировать настоящую неизменяемость, используйте эти коллекции или делайте ручное глубокое копирование.

2
Задача
C# SELF, 19 уровень, 2 лекция
Недоступна
Создание простого record и использование with-выражения
Создание простого record и использование with-выражения
2
Задача
C# SELF, 19 уровень, 2 лекция
Недоступна
Вложенные record и обновление вложенного объекта
Вложенные record и обновление вложенного объекта
Комментарии
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ