Что такое immutable-объекты в C#?

Ответ

Immutable-объекты (неизменяемые объекты) — это объекты, состояние которых невозможно изменить после их создания. Любая операция, которая выглядит как модификация, на самом деле возвращает новый экземпляр объекта с изменёнными данными.

Ключевые преимущества:

  • Потокобезопасность: Не требуют блокировок при использовании в многопоточных сценариях, так как данные только для чтения.
  • Предсказуемость: Отсутствие побочных эффектов упрощает рассуждение о поведении кода.
  • Упрощение отладки: Состояние объекта фиксировано с момента создания.

Примеры в C#:

  1. Тип record (рекомендуемый способ):

    public record Person(string Name, int Age); // Объявление иммутабельного типа
    
    var person1 = new Person("Alice", 30);
    var person2 = person1 with { Age = 31 }; // Создаёт новый экземпляр Person
    Console.WriteLine(person1 == person2); // False - это разные объекты
  2. Тип string:

    string s1 = "Hello";
    string s2 = s1.Replace('H', 'J'); // Возвращает новую строку "Jello", s1 остаётся "Hello"
  3. Реализация вручную (до C# 9):

    public class ImmutablePoint
    {
        public int X { get; } // Свойства только для чтения
        public int Y { get; }
    
        public ImmutablePoint(int x, int y)
        {
            X = x;
            Y = y;
        }
    
        public ImmutablePoint WithX(int newX) => new ImmutablePoint(newX, this.Y);
        public ImmutablePoint WithY(int newY) => new ImmutablePoint(this.X, newY);
    }

Типичные сценарии использования:

  • Объекты-значения (DTO, ViewModel, конфигурации).
  • Ключи в коллекциях (Dictionary, HashSet).
  • Данные, передаваемые между потоками или задачами.
  • Функциональные подходы к программированию.

Ответ 18+ 🔞

А, ну вот, опять эти ваши immutable-объекты, блядь! Сидишь такой, думаешь — ну что за хуйня, зачем они нужны? А потом раз — и поток на тебя налетел, данные попёрлись в разные стороны, и ты уже сидишь с гонками данных, как дурак. А всё потому, что мутабельную хуйню понаписал.

Смотри, по-простому: immutable-объект — это как татуха на жопе. Раз набил — и всё, блядь, не переделаешь. Хочешь новую? Иди, набей новую, но старая-то уже на месте, её не сотрёшь. Вот и тут так же: создал объект — и он застыл, как мужик в сортире после трёх литров пива. Ничего в нём не поменяешь.

Чем это, блядь, хорошо?

  • Потокобезопасность — вообще огонь. Кидаешь такой объект в десять потоков, и хоть бы хны! Все его читают, а изменить не могут, потому что нельзя, сука. Никаких блокировок, мьютексов, семафоров — красота, а не жизнь.
  • Предсказуемость, ёпта. С ним как с бабушкой в деревне — всегда знаешь, чего ожидать. Никаких сюрпризов, что где-то что-то поменялось и всё поехало.
  • Отладка проще некуда. Упало что-то — смотришь на состояние объекта и точно знаешь, что оно с момента создания не менялось. Не надо искать, кто и где его там подпортил.

Ну и как эту радость в C# делать?

  1. record — это вообще песня, блядь! С них и начинай, если, конечно, не на древнем коде сидишь.

    public record Person(string Name, int Age); // Всё, тип готов. Имя и возраст — навсегда.
    
    var person1 = new Person("Alice", 30);
    var person2 = person1 with { Age = 31 }; // Смотри, магия! Новый объект, а старый цел.
    Console.WriteLine(person1 == person2); // False, естественно. Это же разные люди, ёпта!
  2. string — он из коробки такой. Все им пользуются, а многие и не думают, что он immutable.

    string s1 = "Hello";
    string s2 = s1.Replace('H', 'J'); // s2 стала "Jello", а s1 так и осталась "Hello", как ни в чём не бывало.
  3. Ручная работа, старый способ. Раньше, блядь, приходилось горбатиться, как проклятому.

    public class ImmutablePoint
    {
        public int X { get; } // Только геттер, сеттера нет — значит, не изменить, пидорас.
        public int Y { get; }
    
        public ImmutablePoint(int x, int y)
        {
            X = x;
            Y = y;
        }
        // Методы, которые возвращают НОВЫЙ объект, а не меняют текущий.
        public ImmutablePoint WithX(int newX) => new ImmutablePoint(newX, this.Y);
        public ImmutablePoint WithY(int newY) => new ImmutablePoint(this.X, newY);
    }

Где эту прелесть применять?

  • Разные там объекты-значения: настройки, конфиги, данные для отображения. Создал и забыл.
  • Ключи в словарях и хеш-сетах. Главное, чтобы хеш не менялся, а то найдёшь потом свою запись — хуй там.
  • Когда данные между потоками или задачами швыряешь. Кинул и не паришься.
  • Если вдруг на функциональщину подсел — там это вообще основа основ, без этого никуда.

Короче, если хочешь спать спокойно и не бояться, что твой объект где-то там в другом потоке внезапно станет геем — делай его immutable. Оно того стоит, поверь.