В 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
.
Вопрос: почему существует это ограничение? Я предполагаю, что его снятие позволит каким-то образом сломать некоторые функции записей, но это функция достаточно новая, и я не могу придумать, как это сделать. Делает ли автоматически сгенерированный первичный конструктор что-то, что имеет решающее значение для правильной работы записи, и поэтому его нужно вызывать?
Я не уверен, что у меня есть окончательный ответ, но вот мои мысли, основанные на том, что я прочитал. Этот
public record MyRecord(string foo, int bar);
эквивалентно:
public class MyRecord
{
string foo { get; init; } // Code correction - set to init
int bar { get; init; }
}
Конструктор и свойства выводятся в одной строке. Хотя я уверен, что можно добавить конкретное присваивание к этому предполагаемому набору конструкций (и, возможно, это будет сделано в будущей версии), вероятно, будет проще работать с цепочкой конструкторов для первого прохода. Как тот, кто использовал цепные конструкторы в некоторых классах (обычно не в pocos), это имеет смысл. Но я не уверен, сколько раз я буду перегружать конструктор, и я не вижу огромного преимущества, по крайней мере, с архитектурной точки зрения, делая это так, как вы.
@Rudu - отредактировано, чтобы получить правильный код. Он должен был быть инициализирован, а не установлен, что означает, что его можно «установить» только при инициализации.
Основываясь на быстрой декомпиляции 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
в обоих случаях.
Запись неизменяема по умолчанию, поэтому компонент
set;
отсутствует. Документы Что нового в C# 9 хорошо разбирают различия (неизменяемость, равенство на основе значений, переопределение сравнения,ToString
,GetHashCode
, функции копирования и клонирования)