В чем разница между record и struct в C#?

Ответ

record и struct в C# предназначены для моделирования данных, но имеют принципиальные различия в поведении и предназначении.

Record (обычно record class)

  • Тип: Ссылочный тип (по умолчанию — record class). Хранится в управляемой куче (heap).
  • Неизменяемость: Создан для неизменяемых (immutable) данных. Свойства инициализируются при создании и не могут меняться (при использовании позиционного синтаксиса или init-сеттеров).
  • Сравнение по значению: Автоматически генерирует методы Equals, GetHashCode() и операторы ==/!=, которые сравнивают значения всех свойств, а не ссылки.
  • Другие фичи: Встроенная поддержка ToString() с выводом свойств, деконструкции (Deconstruct) и копирования с модификацией (with expressions).
public record Person(string Name, int Age); // Неизменяемый record

var person1 = new Person("Alice", 30);
var person2 = new Person("Alice", 30);

Console.WriteLine(person1 == person2); // True (сравнение по значению)
Console.WriteLine(person1); // Person { Name = Alice, Age = 30 }

var person3 = person1 with { Age = 31 }; // Неизменяемое копирование

Struct (значимый тип)

  • Тип: Значимый тип (value type). Хранится в стеке (stack) или внутри родительского объекта. Копируется по значению при присваивании.
  • Изменяемость: По умолчанию изменяем (mutable), если не объявлен как readonly struct.
  • Сравнение по значению: Не генерирует логику сравнения автоматически. По умолчанию используется ValueType.Equals, который работает через рефлексию и медленно. Для производительности нужно реализовывать IEquatable<T> и переопределять Equals/GetHashCode вручную.
  • Ограничения: Не может иметь конструктора без параметров (до C# 10), не поддерживает наследование.
public struct Point
{
    public int X;
    public int Y;
    // Нет автоматического сравнения по значению!
}

var point1 = new Point { X = 5, Y = 10 };
var point2 = point1; // Копирование значения
point2.X = 20; // Изменяется только копия

Ключевой выбор:

  • Используйте record для неизменяемых моделей данных, DTO, где критично сравнение по значению и нужна встроенная функциональность (ToString, with).
  • Используйте struct для небольших (менее 16 байт), часто создаваемых и копируемых объектов, где важна производительность и размещение в стеке (например, координаты, ключи словарей). Для неизменяемых структур объявляйте readonly struct.

Ответ 18+ 🔞

А, ну это же классика, про которую все такие умные книжки пишут, а на деле всё просто, как три копейки. Смотри, есть у нас в C# две штуки для данных — record и struct. И они, блядь, как братья, но один — отличник, а второй — хулиган из соседнего подъезда.

Record (этот, record class)

  • Что за зверь? Ссылочный тип, то есть живёт в куче, как все нормальные классы. Но не простой, а с понтами.
  • Главная фишка — неизменяемость. Создал — и всё, пиздец. Не перепишешь. Это как договор кровью подписал. Свойства задаются раз и навсегда (ну, или через init, но это уже детали).
  • Сравнивает по значению автоматом. То есть не важно, одна это ссылка в памяти или нет. Если у двух таких record-ов все поля одинаковые — они равны. И Equals, и GetHashCode, и операторы == за тебя уже написали, ленивая жопа.
  • Прочие ништяки: Красивый ToString() сам выводит всё, что внутри, умеет в деконструкцию и, охуенно, в копирование с изменениями через with.
public record Person(string Name, int Age); // Создал — и сиди, не дергайся.

var person1 = new Person("Alice", 30);
var person2 = new Person("Alice", 30);

Console.WriteLine(person1 == person2); // True, потому что значения одинаковые, ёпта!
Console.WriteLine(person1); // Выведет всё красиво: Person { Name = Alice, Age = 30 }

var person3 = person1 with { Age = 31 }; // Волшебство! Новый рекорд, старый не тронут.

Struct (значимый тип)

  • Что за зверь? Значимый тип. Живёт там, где родился — в стеке или внутри родителя. При присваивании тупо копируется целиком, как будто фотографию сделал.
  • Изменяемость? Да запросто! По умолчанию — полный разгул демократии, меняй что хочешь. Хочешь порядок — объявляй readonly struct.
  • Сравнение? Ха-ха, нет. Тут тебе никто ничего автоматически не сгенерирует. По умолчанию будет использоваться медленный как черепаха ValueType.Equals на рефлексии. Хочешь быстро — пиши IEquatable<T> и переопределяй всё сам, вручную, как в старые добрые времена.
  • Ограничения: Наследование — ни-ни. Конструктор без параметров до недавних пор тоже был под запретом.
public struct Point
{
    public int X;
    public int Y;
    // Никакого волшебства. Сравнивай сам, если надо.
}

var point1 = new Point { X = 5, Y = 10 };
var point2 = point1; // Всё, скопировал значение, как будто ксерокснул.
point2.X = 20; // Меняешь копию. Оригинал (point1) похуй.

Так что когда что брать?

  • Берёшь record, когда тебе нужна неизменяемая модель, DTO, или когда заебало вручную реализовывать сравнение. Или просто хочешь выглядеть современно на код-ревью.
  • Берёшь struct, когда делаешь маленькую (желательно, чтобы меньше 16 байт), простую хуйню, которую постоянно копируют, и тебе до зарезу нужна производительность. Или когда твой внутренний перфекционист орет, что объект логически — значение (как та же точка), и ему в куче не место. Для порядка делай его readonly.

Вот и вся философия, без этих ваших умных слов. Выбирай по ситуации и не выёбывайся.