Типы записей C#

Подсказка

Вы новичок в разработке программного обеспечения? Сначала начните с учебников для начинающих. Вы встретите записи, когда вам нужны сжатые типы данных со встроенным равенством.

Есть опыт на другом языке? Записи C# похожи на классы данных в Kotlin или классах регистра в Scala. Они оптимизированы для хранения данных, с равенством, генерируемым компилятором, и семантикой копирования. Просмотрите разделы record class vs record struct и выражений with для шаблонов, специфичных для C#, чтобы найти характерные шаблоны.

Ключевое слово record является модификатором, применяемым к class или struct. Он сообщает компилятору создать равенство значений, отформатированную ToStringи недеструктивную мутацию через with выражения. Базовый тип, класс или структуру, по-прежнему определяет, используют ли экземпляры семантику ссылок или значений. Модификатор record добавляет понятное для данных поведение поверх этих семантик. Используйте записи, когда основная роль типа хранит данные и два экземпляра с одинаковыми значениями следует считать равными.

Когда следует использовать записи

Используйте запись, когда выполняются все следующие условия:

  • Основная роль типа — хранение данных.
  • Два экземпляра с одинаковыми значениями должны быть равными.
  • Требуется неизменяемость (особенно для record class типов).
  • Вы хотите удобочитаемую ToString , не записывая ее вручную.

При выборе между record class и record struct:

  • Используйте record class когда требуется наследование или когда тип достаточно велик, чтобы копирование при каждом назначении было дорогостоящим.
  • Используйте record struct для небольших автономных значений, когда полезны семантика копирования и выделение на стеке.

Избегайте записей типов сущностей в Entity Framework Core, которые зависят от ссылочного равенства для отслеживания сущностей. Более широкое сравнение параметров типа см. в разделе "Выбор типа".

Объявить запись

Можно применить record к классу или структуре. Простейшая форма использует позиционные параметры , определяющие конструктор и свойства в одной строке:

public record Person(string FirstName, string LastName);

Тот же позиционный синтаксис работает для record struct типов:

public record struct Coordinate(double Latitude, double Longitude);

public readonly record struct Temperature(double Celsius)
{
    public double Fahrenheit => Celsius * 9.0 / 5.0 + 32.0;
}

Написание record является сокращением для record class, который является ссылочным типом. Запись record struct создает тип значения. Компилятор создает свойства из позиционных параметров в обоих случаях, но значения по умолчанию отличаются:

  • record class: свойства имеют значение init-only (неизменяемые после построения).
  • record struct: свойства по умолчанию доступны для записи и чтения. Добавьте readonly (readonly record struct), чтобы сделать их только init.

Записи также можно записывать с помощью стандартного синтаксиса свойств, если вам нужен дополнительный элемент управления. Например, чтобы сделать свойство чтение и запись вместо init-only:

public record Product
{
    public required string Name { get; init; }
    public decimal Price { get; set; }
}

record class и record struct

Поскольку модификатор сохраняет семантику базового типа, record и record class ведут себя по-другому при назначении или сравнении ссылок. Назначение record class копирует ссылку. Обе переменные указывают на один и тот же объект. Назначение record struct копирует данные, поэтому изменения одной переменной не влияют на другую.

// Record class — assignment copies the reference
var p1 = new Person("Grace", "Hopper");
var p2 = p1; // p1 and p2 point to the same object:
Console.WriteLine(ReferenceEquals(p1, p2)); // True

// Record struct — assignment copies the data
var c1 = new Coordinate(47.6062, -122.3321);
var c2 = c1;
c2.Longitude = 0.0; // mutating c2 doesn't affect c1
Console.WriteLine(c1.Longitude); // -122.3321
Console.WriteLine(c2.Longitude); // 0

Структуры записей также обеспечивают равенство значений, созданных компилятором:

var home = new Coordinate(47.6062, -122.3321);
var copy = home;

Console.WriteLine(home);           // Coordinate { Latitude = 47.6062, Longitude = -122.3321 }
Console.WriteLine(home == copy);   // True — value equality

Выберите record class , когда требуется наследование или когда экземпляры достаточно большие, чтобы копирование было дорогостоящим. Выберите record struct для небольших автономных данных, где семантика копирования, характерная для типов-значений, подходит. Дополнительные сведения о семантике типа значений см. в разделе "Структуры".

Равенство значений

Модификатор record предоставляет как классы, так и структуры, созданные компилятором, равенство свойств по свойству. Вот как равенство работает во всех четырех типах:

  • Обычный класс: использует равенство ссылок по умолчанию. Оператор == проверяет, указывают ли две переменные на один и тот же объект, а не совпадают ли данные.
  • Простая структура: поддерживает равенство ValueType.Equalsзначений, но реализация по умолчанию использует отражение, которое медленнее и не создает ==/!= операторы.
  • record class: компилятор создает методы Equals и GetHashCode, а также операторы ==/!=, которые сравнивают каждое значение свойства. Два разных объекта с одинаковыми данными равны.
  • record struct: То же равенство, сгенерированное компилятором для record class, но без использования отражения, что делает его быстрее, чем обычное равенство структур.

В следующем примере показано равенство классов записей:

// Person is a record type with three properties: FirstName, LastName, and PhoneNumbers.
var phones = new string[] { "555-1234" };
var person1 = new Person("Grace", "Hopper", phones);
var person2 = new Person("Grace", "Hopper", phones);

Console.WriteLine(person1 == person2);              // True
Console.WriteLine(ReferenceEquals(person1, person2)); // False

person1.PhoneNumbers[0] = "555-9999";
Console.WriteLine(person2.PhoneNumbers[0]); // 555-9999 — same array

Два Person экземпляра являются разными объектами, но они равны, так как все их значения свойств соответствуют. Свойства массива сравниваются по ссылке, а не по содержимому. Изменение общего массива отображается с помощью обеих записей, так как сам массив не копируется.

Недеструктивная мутация с with выражениями

Записи часто неизменяемы, поэтому вы не можете изменить свойство после создания. with Выражение создает копию с одним или несколькими свойствами, оставляя исходную запись без изменений. Этот подход работает для обоих типов: record class и record struct

var original = new Person("Grace", "Hopper");
var modified = original with { FirstName = "Margaret" };

Console.WriteLine(original); // Person { FirstName = Grace, LastName = Hopper }
Console.WriteLine(modified); // Person { FirstName = Margaret, LastName = Hopper }
Console.WriteLine(original == modified); // False

var copy = original with { };
Console.WriteLine(original == copy); // True

Тот же синтаксис работает для record struct типов:

var shifted = home with { Longitude = -122.0 };
Console.WriteLine(shifted);        // Coordinate { Latitude = 47.6062, Longitude = -122 }
Console.WriteLine(home == shifted); // False

Выражение with копирует существующий экземпляр, а затем применяет указанные изменения свойств.

Позиционные записи и деконструкция

Позиционные записи генерируют метод Deconstruct, который вы используете для извлечения значений свойств в отдельные переменные:

var (first, last) = person;
Console.WriteLine($"{first} {last}");
// Grace Hopper

Деконструкция работает с обоими record class и record struct типами. Его можно использовать в сопоставлениях назначений, foreach циклов и шаблонов.

Наследование записей

Один record class может наследоваться от другого record class. Запись не может наследоваться от обычного класса, и класс не может наследовать от записи:

public record Student(string FirstName, string LastName, int GradeLevel)
    : Person(FirstName, LastName);

Проверки равенства значений включают тип времени выполнения, поэтому Person и Student с одинаковыми FirstName и LastName не считаются равными. Структуры записей не поддерживают наследование, так как структуры не могут наследоваться от других типов.

См. также