Среди нововведений C# 12 было достаточно больше количество по-настоящему качественных и крутых фич (например дефолтные параметры лямбд).  Но речь сегодня пойдет о ложке дёгтя в бочке мёда - Primary Constructors. Вот казалось бы, как здесь можно напортачить? Идея взята прямиком из Kotlin, все что надо было сделать это перенести известную, успешно работающую функциональность из одного языка в другой. Всё. Как говорится, воруй как художник? Думаю, что это не про Primary Constructors, потому что насколько плохо своровать фичу это надо было постараться. Почему же в Kotlin такие конструкторы работают, а в C# нет? Давайте разбираться. На мой взгляд, две самых главных проблемы C# как языка - это многословность и двусмысленность. Под многословностью я подразумеваю возможность получить одинаковый результат разными способами. Под двусмысленностью я подразумеваю конструкцию языка, которая может толковаться по-разному. Возьмём пример из новенького релиза .NET 8: Почему мы получаем True если наполнение структур разное?  В случае если параметр  Primary Constroctor'а не применяется в свойстве или методе, то он не будет сохраняться как поле. А так как семантика  Equals у структур это value equality, и сравнение происходит по полям - а не сгенерировались, то и результат будет True. Получается так, что синтаксис для record и для struct/class абсолютно идентичен, 1 в 1, но при этом, генерируемый под капотом код и поведение будет абсолютно другое. Отлично. Еще одна двусмысленность в и так переполненном двусмысленностью языке. Но в Кotlin же мы можем пометить поле как сохраняемое!  Наверное и в C# 12.0 это можно сделать, ведь при проектировании такой важной фичи был предусмотрен настолько очевидный кейс? Правда ведь? Конечно же нет! Так вот, почему это плохо. Потому что это максимально неинтуитивно. Каждый элемент двусмысленности добавляет путаницу, добавляет куски информации, про которые необходимо всегда помнить, чтобы эффективно писать на языке. Теперь каждый разработчик на шарпе обязан знать 2 новых факта: Если ты не используешь параметр Primary Constructor'а, то он не будет сохраняться как поле в объекта в типе Синтаксис class A(int B)/struct A(int B) и record A(int B) - это не одно и то же. В случае struct/class'ов это Primary Constructor, в случае record - это НЕ так. Если этого не знать, то это прямая дорога сломанному проду. Не написал тест? Использовал структуру с Primary Constroctor'ом и не знал про нюанс сохранения полей? Лови инцидент. Что происходит с прихраниваемыми значениями primary constructor'ов? Они становятся неизменяемыми? Правда ведь? Конечно же нет! Но в Кotlin же мы можем пометить параметр конструктора как иммутабельный! Наверное и в C# 12.0 это можно сделать, ведь при проектировании такой важной фичи был предусмотрен настолько очевидный кейс? Да? Да??? Конечно же НЕТ! А что насчёт наследования? Смогу ли я использовать параметр primary constructor'а из наследуемого класса? Конечно же нет! Все аргументы primary constructor'ов исключительно приватные: Но в Кotlin же мы можем добавлять уровни доступа к параметрам конструктора! Наверное и в C# 12.0 это можно сделать, ведь при проектировании такой важной фичи был предусмотрен настолько очевидный кейс? Правда ведь?! Давайте на 1. 2. 3 - Конечно же .НET! Помогите Даше ПутеШарпишечнице найти все способы создать иммутабельные поля в C# 12.0! Я смог придумать целых восемь разных способов. Поправьте меня если я что-то упустил:  Пример выше, это даже без учета статических фабричных методов. Вместе с ними, в комбинациях можно собрать мать его фуллхаус, а кол-во способов смело на 2 умножать. При этом, среди вариантов были логически странные способы. Например: Выводиться будет 2, тут плюс-минус всё понятно, но интересность конструкции на этом не заканчивается. Из-за комбинации кейвордов, получается очень интересная ситуация - мы сможем создать объект, только если оба и конструктор и object initializer будут использованы при создании: Вроде баг мелкий, но все равно неприятно. Я уверен что кто-то уже создал issue для анализатора, я уверен что в одном из первых минорных релизов это пофиксают. Но сам факт остается неприятным В итоге что мы имеем? Мы получили какой-то обрубок фичи, в которой: Нельзя форсить поведение сохранения в поле (как например var/val в kotlin) Нельзя выбирать уровень доступа (private, public, protected, etc) Нельзя форсить иммутабельность значения - все параметры Primary Constructor'а являются мутабельными и этого нельзя изменить. Поведение для record и не record типов значительно отличается, нет единообразия. Кривые пересечения кейвордов и недоработанный анализатор. Появляется 4 новых способа инициализации типов и иммутабельных переменных, часть из которых являются логически неправильными. Появляется 2 новых факта про которые надо всегда помнить при разработке/ревью. Итак, если собрать все воедино, в чем выражается моя претензия? Когда речь идет о дизайне языков, я всегда вспоминаю две крайности. Первая - это Го. Элегентно простой, с идеологией "одну вещь делаем одним способом", и мне это нравится. Другая крайность - плюсы/тайпскрипт, у которых есть 20 разных способов описания цикла. В итоге все сводится к тому, что каждый пишет как хочет, внутри языка появляются диалекты среди разных команд (взять например ту же дилемму выбора type vs interface для описания типов пропсов компонентов в React). Шарп активно идёт в сторону переусложнения, и на мой личный взгляд, это полная дичь. Я не хочу чтобы это произошло. Мне хочется думать об архитектуре и домене, но никак о том какой из 7-ми почти идентичных способов использовать для создания иммутабельного типа. Такие дела.  Гав гав… Ой… Кря кря?