8 вещей о Records в C#, о которых вы, вероятно, не знали

Records — это новый тип данных, представленный в 2021 году в C# 9 и .NET Core 5.

public record Person(string Name, int Id);
Вход в полноэкранный режим Выход из полноэкранного режима

Записи — это третий способ определения типов данных в C#; два других — class и struct.

Поскольку это довольно новая идея в .NET, мы должны потратить некоторое время на эксперименты с ней и попытаться понять ее возможности и функциональность.

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

1 — Записи неизменяемы

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

var me = new Person("Davide", 1);
me.Name = "AnotherMe"; // won't compile!
Войти в полноэкранный режим выйти из полноэкранного режима

Эта операция не является законной.

Даже компилятор жалуется:

Init-only property or indexer ‘Person.Name’ can only be assigned in an object initializer, or on ‘this’ or ‘base’ in an instance constructor or an ‘init’ accessor.

2- Записи реализуют равенство

Другим основным свойством записей является то, что они реализуют равенство «из коробки».

[Test]
public void EquivalentInstances_AreEqual()
{
    var me = new Person("Davide", 1);
    var anotherMe = new Person("Davide", 1);

    Assert.That(anotherMe, Is.EqualTo(me));
    Assert.That(me, Is.Not.SameAs(anotherMe));
}
Вход в полноэкранный режим Выход из полноэкранного режима

Как вы видите, я создал два экземпляра Person с одинаковыми полями. Они считаются равными, но не являются одним и тем же экземпляром.

3- Записи можно клонировать или обновлять с помощью ‘with’

Итак, если нам нужно обновить поле записи, что мы можем сделать?

Мы можем использовать ключевое слово with:

[Test]
public void WithProperty_CreatesNewInstance()
{
    var me = new Person("Davide", 1);
    var anotherMe = me with { Id = 2 };

    Assert.That(anotherMe, Is.Not.EqualTo(me));
    Assert.That(me, Is.Not.SameAs(anotherMe));
}
Войти в полноэкранный режим Выйти из полноэкранного режима

Посмотрите на me with { Id = 2 }: эта операция создает клон me и обновляет поле Id.

Конечно, вы можете использовать with для создания нового экземпляра, идентичного исходному.

[Test]
public void With_CreatesNewInstance()
{
    var me = new Person("Davide", 1);

    var anotherMe = me with { };

    Assert.That(anotherMe, Is.EqualTo(me));
    Assert.That(me, Is.Not.SameAs(anotherMe));
}
Вход в полноэкранный режим Выход из полноэкранного режима

4- Записи могут быть структурами и классами

В основном, записи действуют как классы.

public record Person(string Name, int Id);
Вход в полноэкранный режим Выйти из полноэкранного режима

Иногда это не то, что вам нужно. Начиная с C# 10 вы можете объявлять записи как структуры:

public record struct Point(int X, int Y);
Войти в полноэкранный режим Выход из полноэкранного режима

Очевидно, что все, что мы видели раньше, остается в силе.

[Test]
public void EquivalentStructsInstances_AreEqual()
{
    var a = new Point(2, 1);
    var b = new Point(2, 1);

    Assert.That(b, Is.EqualTo(a));
    //Assert.That(a, Is.Not.SameAs(b));// does not compile!
}
Вход в полноэкранный режим Выход из полноэкранного режима

Ну, почти все: вы не можете использовать Is.SameAs(), потому что, поскольку структуры являются типами значений, два значения всегда будут разными значениями. Компилятор сообщит вам об этом, выдав ошибку, которая гласит:

Ограничение SameAs всегда не работает для типов значений, так как фактическое и ожидаемое значение не могут быть одной и той же ссылкой.

5 — Записи на самом деле не являются неизменяемыми

Мы видели, что вы не можете обновлять существующие записи. Это не совсем верно.

Это утверждение верно в случае «простых» записей, таких как Person:

public record Person(string Name, int Id);
Войти в полноэкранный режим Выйти из полноэкранного режима

Но все меняется, когда мы используем другой способ определения Записей:

public record Pair
{
    public Pair(string Key, string Value)
    {
        this.Key = Key;
        this.Value = Value;
    }

    public string Key { get; set; }
    public string Value { get; set; }
}
Войти в полноэкранный режим Выйти из полноэкранного режима

Мы можем явно объявить свойства Записи, чтобы сделать ее более похожей на обычные классы.

Используя этот подход, мы все еще можем использовать функциональность автоматического равенства Записей

[Test]
public void ComplexRecordsAreEquatable()
{
    var a = new Pair("Capital", "Roma");
    var b = new Pair("Capital", "Roma");

    Assert.That(b, Is.EqualTo(a));
}
Вход в полноэкранный режим Выйти из полноэкранного режима

Но мы можем обновить одно поле, не создавая совершенно новый экземпляр:

[Test]
public void ComplexRecordsAreNotImmutable()
{
    var b = new Pair("Capital", "Roma");
    b.Value = "Torino";

    Assert.That(b.Value, Is.EqualTo("Torino"));
}
Вход в полноэкранный режим Выход из полноэкранного режима

Кроме того, только простые типы являются неизменяемыми, даже при использовании базового определения Record.

Тип ComplexPair — это Record, который принимает в определении список строк.

public record ComplexPair(string Key, string Value, List<string> Metadata);
Вход в полноэкранный режим Выход из полноэкранного режима

Этот список строк не является неизменным: вы можете добавлять и удалять элементы по своему усмотрению:

[Test]
public void ComplexRecordsAreNotImmutable2()
{
    var b = new ComplexPair("Capital", "Roma", new List<string> { "City" });
    b.Metadata.Add("Another Value");

    Assert.That(b.Metadata.Count, Is.EqualTo(2));
}
Вход в полноэкранный режим Выйти из полноэкранного режима

В примере ниже видно, что я добавил новый элемент в список Metadata без создания нового объекта.

6- Записи могут иметь подтипы

Интересной особенностью является то, что мы можем создавать иерархию записей очень простым способом.

Помните определение Person?

public record Person(string Name, int Id);
Вход в полноэкранный режим Выход из полноэкранного режима

Так вот, вы можете определить подтип так же, как вы это делаете с обычными классами:

public record Employee(string Name, int Id, string Role) : Person(Name, Id);
Войти в полноэкранный режим Выйти из полноэкранного режима

Конечно, все правила Boxing и Unboxing остаются в силе.

[Test]
public void Records_CanHaveSubtypes()
{
    Person meEmp = new Employee("Davide", 1, "Chief");

    Assert.That(meEmp, Is.AssignableTo<Employee>());
    Assert.That(meEmp, Is.AssignableTo<Person>());
}
Войти в полноэкранный режим Выход из полноэкранного режима

7 — Записи могут быть абстрактными

…и да, у нас могут быть абстрактные записи!

public abstract record Box(int Volume, string Material);
Вход в полноэкранный режим Выйти из полноэкранного режима

Это означает, что мы не можем создавать новые Записи, тип которых помечен как Абстрактный.

var box = new Box(2, "Glass"); // cannot create it, it's abstract
Вход в полноэкранный режим Выход из полноэкранного режима

Напротив, для инстанцирования новых объектов нам необходимо создавать конкретные типы:

public record PlasticBox(int Volume) : Box(Volume, "Plastic");
Войти в полноэкранный режим Выход из полноэкранного режима

Опять же, все правила, которые мы уже знаем, остаются в силе.

[Test]
public void Records_CanBeAbstract()
{
    var plasticBox = new PlasticBox(2);

    Assert.That(plasticBox, Is.AssignableTo<Box>());
    Assert.That(plasticBox, Is.AssignableTo<PlasticBox>());
}
Вход в полноэкранный режим Выход из полноэкранного режима

8- Запись может быть опечатана

Наконец, записи могут быть помечены как опечатанные.

public sealed record Point3D(int X, int Y, int Z);
Вход в полноэкранный режим Выход из полноэкранного режима

Пометить запись как запечатанную означает, что мы не можем объявлять подтипы.

public record ColoredPoint3D(int X, int Y, int Z, string RgbColor) : Point3D(X, Y, X); // Will not compile!
Войти в полноэкранный режим Выйти из полноэкранного режима

Это может быть полезно при открытии типов для внешних систем.

Дополнительные ресурсы

Как обычно, несколько ссылок, которые вы можете прочитать, чтобы узнать больше о записях в C#.

Первая из них — это учебник с сайта Microsoft, который обучает основам Records:

🔗 Создание типов записей | Microsoft Docs

Второй — великолепная статья Гэри Вудфайна, в которой он исследует внутренние аспекты C# Records и многое другое:

🔗C# Records — The good, bad & ugly | Gary Woodfine.

Наконец, если вам интересны мелочи о C#, которые мы используем, но редко изучаем, вот статья, которую я написал некоторое время назад о GUIDs в C# — вы найдете там много интересного!

🔗5 вещей, которые вы не знали о Guid в C# | Code4IT

Эта статья впервые появилась на Code4IT

Подведение итогов

В этой статье мы рассмотрели 8 вещей, которые вы, вероятно, не знали о Records в C#.

Записи являются довольно новым явлением в экосистеме .NET, поэтому мы можем ожидать новых обновлений и функциональных возможностей.

Есть ли что-то еще, что мы должны добавить? Или, может быть, что-то, чего вы не ожидали?

Счастливого кодинга!

🐧

Оцените статью
devanswers.ru
Добавить комментарий