Мысли о Первичных Конструкторах. Окончание
Начало
Подводные камни
Замечание: описанные ниже «проблемы» не означают, что нельзя использовать первичные конструкторы, и для вас они даже могут показаться несущественными.
1. Повторный захват
Если вы инициализируете поле с помощью параметра первичного конструктора, а также используете его значение в другом члене класса, у вас будет два поля, хранящих одно и то же значение:
public class Person(string name)
{
private string _name = name;
public string Greeting
=> $"Hello, {name}";
}
Эта проблема настолько очевидна, что компилятор выдаст предупреждение, если вы сделаете это, так что тут особо беспокоиться не о чем.
2. Неявные поля не могут быть readonly
Если вы неявно захватываете параметр первичного конструктора, компилятор генерирует поле для его хранения (см. подробнее). Важно, что они изменяемы, т.е. параметру (и этому полю) можно задавать значения в теле класса. Если это то, что вам нужно, нет проблем. Но если поле не должно быть изменяемо, обычно стоит отметить его как readonly, что упрощает понимание назначения поля и устраняет целый класс ошибок, связанных со случайным изменением поля. Не удивлюсь, если это исправят в будущих версиях C#, но сейчас это немного раздражает. Решение в том, чтобы не использовать неявный захват, если вам нужны поля только для чтения.
3. Путаница в соглашениях об именах
Не большая проблема, но это придётся обсудить в команде на раннем этапе: какие соглашения следует использовать для параметров первичного конструктора? Использовать ли для параметров префикс
_? Если вы используете неявный захват в коде класса, то там они будут по сути как приватные поля:public class Person(string _firstName, string _lastName)
{
public string FullName => $"{_firstName} {_lastName}";
}
С другой стороны, создание экземпляра такого класса будет выглядеть… странно:
var p = new Person(_firstName: "Jon", _lastName: "Smith");
Microsoft рекомендует именование как параметров (без префикса). Кстати, вы не можете использовать
this. в коде для обращения к параметрам первичного конструктора, такой код просто не скомпилируется.4. Неявные поля меняют размер структур
В высокопроизводительном коде или при работе с interop иногда бывает важен размер экземпляров и как выстроено их содержимое. Если у нас есть следующая структура:
public struct Person(
string first,
string last,
int age)
{
public int Age = age;
public string FullName => $"{first} {last}";
}
То мы имеем 3 поля (1 – int и 2 – ссылки на string) для неявных полей, всего 8*3=24 байта на экземпляр. Теперь, если мы изменим первичный конструктор:
public struct Person(
string name,
int age)
{
public int Age = age;
public string FullName => name;
}
Внезапно вместо 3 полей останется 2 и размер станет 16 байт, но это без явного изменения публичных полей структуры (если вы не знаете, как реализованы первичные конструкторы). Это может иметь очень плохие последствия, если мы полагаемся на размер Person где-то в коде!
5. Путаница с записями
Всё-таки стоит помнить, что, несмотря на почти идентичный синтаксис, следующие объявления не взаимозаменяемы:
public record Person(string FirstName, string LastName);
public class Person(string FirstName, string LastName);
Первичные конструкторы не создают публичных полей нигде, кроме типов записей. Поэтому, если вы вдруг решите сменить запись на класс, то ваш код внезапно перестанет компилироваться.
Источник: https://andrewlock.net/thoughts-about-primary-constructors-3-pros-and-5-cons/