Почему в записях с первичным конструктором требуется явный инициализатор конструктора this?

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

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

Итак, это запрещено:

public record A(string Foo, int Bar)
{
    public A(MyClass x)
    {
        Foo = x.Name;
        Bar = x.Number;
    }
}

и действительно вызывает CS8862 «Конструктор, объявленный в записи со списком параметров, должен иметь инициализатор конструктора this». Мы должны написать:

public record A(string Foo, int Bar)
{
    public A(MyClass x) : this(x.Name, x.Number) {}
}

вместо. В данном случае это вряд ли проблема, но можно представить себе гораздо более длинную логику инициализации, которая просто не вписывается в инициализатор конструктора this.

Вопрос: почему существует это ограничение? Я предполагаю, что его снятие позволит каким-то образом сломать некоторые функции записей, но это функция достаточно новая, и я не могу придумать, как это сделать. Делает ли автоматически сгенерированный первичный конструктор что-то, что имеет решающее значение для правильной работы записи, и поэтому его нужно вызывать?


Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
26
0
8 194
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Я не уверен, что у меня есть окончательный ответ, но вот мои мысли, основанные на том, что я прочитал. Этот

public record MyRecord(string foo, int bar);

эквивалентно:

public class MyRecord 
{
    string foo { get; init; } // Code correction - set to init
    int bar { get; init; }
}

Конструктор и свойства выводятся в одной строке. Хотя я уверен, что можно добавить конкретное присваивание к этому предполагаемому набору конструкций (и, возможно, это будет сделано в будущей версии), вероятно, будет проще работать с цепочкой конструкторов для первого прохода. Как тот, кто использовал цепные конструкторы в некоторых классах (обычно не в pocos), это имеет смысл. Но я не уверен, сколько раз я буду перегружать конструктор, и я не вижу огромного преимущества, по крайней мере, с архитектурной точки зрения, делая это так, как вы.

Запись неизменяема по умолчанию, поэтому компонент set; отсутствует. Документы Что нового в C# 9 хорошо разбирают различия (неизменяемость, равенство на основе значений, переопределение сравнения, ToString, GetHashCode, функции копирования и клонирования)

Rudu 21.12.2020 18:56

@Rudu - отредактировано, чтобы получить правильный код. Он должен был быть инициализирован, а не установлен, что означает, что его можно «установить» только при инициализации.

Gregory A Beamer 21.12.2020 21:19

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

public A(string Foo, int Bar)
{
    this.Foo = Foo;
    this.Bar = Bar;
    base..ctor();
}

public A(MyClass x)
    : this(x.Name, x.Number)
{
}

Запись предполагает, что все свойства будут инициализированы через конструктор. Если бы свойства можно было произвольно отображать в дополнительном конструкторе без явного вызова this, не было бы (сразу очевидного) способа гарантировать выполнение всех требований к параметрам свойств. В результате для принудительного сопоставления свойств требуется вызов this(params).

Ответ принят как подходящий

Это связано с тем, что первичные параметры конструктора немного особенные — они находятся в области видимости на протяжении всей инициализации записи. Угадайте, что напечатает следующая программа:

System.Console.WriteLine(new Foo(Bar: 42).Baz);

public record Foo(int Bar) {
    public int Bar => 41;
    public int Baz = Bar;
}

41 или 42?

И ответ...

барабанную дробь пожалуйста...

42!

Что тут происходит?

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

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

System.Console.WriteLine(new Foo().Baz);

public record Foo(int Bar) {
    public Foo(){}
    public int Bar => 41;
    public int Baz = Bar; //  What is Bar here when the primary constructor isn't called.
}

В стороне

Параметр Bar доступен только во время инициализации. Вместо этого после инициализации свойство Bar находится в области видимости. Если бы мы немного изменили наш пример:

System.Console.WriteLine(new Foo(Bar: 42).Baz);

public record Foo(int Bar) {
    public int Bar => 41;
    public int Baz => Bar; //Note this is `=>` not `=`
}

Это напечатает 41.

Ваша сторона относится к Bar в обоих случаях.

Ian Kemp 26.05.2022 16:34

Другие вопросы по теме