﻿# О дивный новый C\#

В следующем месяце выходит очередная версия нашего любимого языка программирования\. Чем не повод присесть на кухне с рюмкой чая и обсудить, что не так с современными версиями C\#?

![1348_brave_new_csharp_ru/image1.png](https://import.viva64.com/docx/blog/1348_brave_new_csharp_ru/image1.png)


> Мы опубликовали и перевели \[эту\]\(https://habr\.com/ru/articles/959980/\) статью с разрешения правообладателя\\\. Автор статьи — Максим Добросельский \\\(\[melanchall\]\(https://github\.com/melanchall\)\\\)\\\.




> \*\*Примечание\*\*\\\. Статья была написана до релиза \\\.NET 10\\\.



## Вступление

В студенческие годы была у меня книга небезызвестного датчанина о небезызвестном языке программирования\. И хотя я испытывал неподдельные тёплые чувства к C\+\+, книгу ту я не осилил даже наполовину \(настолько унылым было чтиво\)\. Тем не менее, это не помешало мне продолжать верить в предначертанную нам с плюсами судьбу и летом 2011\-го пойти устраиваться программистом на этом языке\.

Итог собеседования был следующий: C\+\+ я не знаю\. Но мне было предложено попробовать себя в C\#\. Чувства были задеты, но я согласился: хотелось уже начать работать\.

Тестовое задание заключалось в написании игры "Змейка"\. Для усложнения поле представляло собой карту России, передвигаться можно было только в её пределах, а расти предлагалось за счёт поедания крупных городов\. Игра должна была запускаться в браузере с использованием ныне покойного [Silverlight](https://en.wikipedia.org/wiki/Microsoft_Silverlight)\.

Приложение было написано и одобрено, я был принят на своё первое место работы, а С\+\+ остался в прошлом\. Так волею судеб я стал разработчиком на C\#, полюбил этот язык и по сей день неразлучен с ним\. К сожалению, иногда у меня возникает чувство, что с ним не всё в порядке\.

## Оглавление

* [А есть ли проблема?](https://habr.com/ru/articles/959980/#problem)
* [Точка отсчёта](https://habr.com/ru/articles/959980/#origin)
* [Когда остановиться невозможно](https://habr.com/ru/articles/959980/#cantstop)
* [Когда нужно больше ключевых слов](https://habr.com/ru/articles/959980/#morekeywords)
* [Когда догадаться сложно](https://habr.com/ru/articles/959980/#cantguess)
* [Заключение](https://habr.com/ru/articles/959980/#conclusion)

## А есть ли проблема?

Рано или поздно любой программист, увлекающийся C\+\+, приходит к выводу: _"Никто не знает этот язык полностью"_\. Но, хотя наши с плюсами дороги разошлись много лет назад, в последние годы мысль эта стала всплывать у меня снова\. Правда, в слегка изменённом виде: вместо \+\+ я вижу там \#\. Конечно, шарпу ещё очень далеко до сложности творения Бьёрна Страуструпа\. Однако многие новшества начинают вызывать обеспокоенность за былое изящество C\#\.

Но прежде чем мы начнём аккуратно раскладывать на лабораторном стекле синтаксические конструкции, отвечу на абсолютно справедливое замечание: _"Никто ведь не заставляет пользоваться новыми фичами"_\. Т\.е\. проблемы по факту нет, и зачем тогда весь этот разговор? Тут интерес скорее личный\.

Я, как разработчик [\.NET библиотеки](https://github.com/melanchall/drywetmidi), иногда сталкиваюсь с запросами пользователей на расширение API\. Бывает, что я и сам вижу в чужом коде почву для новых функций или переработки старых\. Но целесообразны ли изменения? Насколько увеличится сложность API? Так ли неудобно решать определённые задачи при нынешнем устройстве библиотеки?

Лаконичность контрактов и простота решения реальных пользовательских проблем — параметры, между которыми постоянно приходится балансировать\. И мне очень интересно, как с такой эквилибристикой справляются в Microsoft при разработке инструмента, которым я пользуюсь ежедневно\.

Безусловно, любой язык программирования должен развиваться\. Да чего уж скрывать, я и сам использую многие новинки\. Но так ли без них было сложно и неудобно? И хотя мои претензии могут звучать ультраконсервативно в духе _"Раньше байты были восьмибитнее"_, хочется поделиться своими мыслями и обсудить их вместе с вами\.

## Точка отсчёта

Согласно [официальной летописи](https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-version-history), C\# к моменту написания статьи насчитывает 17 ревизий\. Любопытно, что первые семь версий \(до шестой включительно\) охватывают период в 13 лет, тогда как остальные десять были выпущены всего за 8:

![1348_brave_new_csharp_ru/image2.png](https://import.viva64.com/docx/blog/1348_brave_new_csharp_ru/image2.png)

Стоит заметить, что, в отличие от кажущегося хаотическим раннего графика релизов, в наши дни новые версии языка стоит ожидать ежегодно в ноябре, ибо они привязаны к [выпускам \.NET](https://dotnet.microsoft.com/en-us/platform/support/policy):

![1348_brave_new_csharp_ru/image3.png](https://import.viva64.com/docx/blog/1348_brave_new_csharp_ru/image3.png)

Бурный рост количества фич в C\# начался примерно между седьмой и восьмой версиями\. Вот, например, график увеличения количества ключевых слов \(keywords\):

<details>
   <summary>Как собраны данные</summary>

1. Исходники страницы [C\# Keywords](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords) из официальной документации лежат в файле [docs/docs/csharp/language\-reference/keywords/index\.md](https://github.com/dotnet/docs/blob/main/docs/csharp/language-reference/keywords/index.md)\. Поэтому взяты изменения этого файла и собраны списки ключевых слов\.
1. Но коммиты есть только до ноября 2016\-го\. Данные о перечне ключевых слов до этой даты собраны вручную из архивов MSDN\.
1. В какие\-то моменты времени одни и те же ключевые слова считались разными в зависимости от контекста \(например, `partial` при объявлении классов и методов\)\. При сборе данных такого разделения не делалось\.


</details>
![1348_brave_new_csharp_ru/image4.png](https://import.viva64.com/docx/blog/1348_brave_new_csharp_ru/image4.png)

Можно также взглянуть на содержимое файла [Syntax\.xml](https://github.com/dotnet/roslyn/blob/main/src/Compilers/CSharp/Portable/Syntax/Syntax.xml) в репозитории [dotnet/roslyn](https://github.com/dotnet/roslyn)\. Он используется для генерации компилятора Roslyn и содержит все синтаксические правила C\#\. Отсчёт начнём с релиза Visual Studio 2015 RTM, т\.е\. с июля 2015\-го, когда Roslyn [стал](https://learn.microsoft.com/en-us/visualstudio/extensibility/roslyn-version-support) компилятором по умолчанию в VS \(стало быть, начал поддерживать все фичи языка\)\. Вот как с годами менялось количество узлов в указанном файле:

<details>
   <summary>Как собраны данные</summary>

1. Взяты коммиты файла [Syntax\.xml](https://github.com/dotnet/roslyn/blob/main/src/Compilers/CSharp/Portable/Syntax/Syntax.xml)\. Файл два раза в истории перемещался/переименовывался, изменения отслежены вплоть до первой версии исходного документа\.
1. Для каждого коммита собрано количество дочерних узлов элемента `Tree`\.


</details>
![1348_brave_new_csharp_ru/image5.png](https://import.viva64.com/docx/blog/1348_brave_new_csharp_ru/image5.png)

Помимо того, что Microsoft и сама начала активно развивать язык, компания предоставила всем нам возможность предлагать фичи и голосовать за них в соответствующем [репозитории](https://github.com/dotnet/csharplang) на GitHub\. Поэтому многие кинулись создавать запросы, в которых описывали свою профессиональную боль и способы её решения через новые языковые конструкции\. Каюсь, за мной тоже числятся такие предложения \([раз](https://github.com/dotnet/csharplang/issues/742) и [два](https://github.com/dotnet/csharplang/issues/842)\)\. И я очень рад, что они были отклонены\.

Если зайти в папку [proposals](https://github.com/dotnet/csharplang/tree/main/proposals), то стартовать подпапки будут с шестой версии\. Но в соответствующей [директории](https://github.com/dotnet/csharplang/tree/main/proposals/csharp-6.0) всего три небольших файла\. Вероятно, это ещё были внутренние хотелки команды разработчиков языка\. А вот в [папке](https://github.com/dotnet/csharplang/tree/main/proposals/csharp-7.0) с материалами по седьмой версии будут уже документы со ссылками на дискуссии\. Здесь мы и приходим к точке отсчёта, когда стали проскакивать сомнительные \(с моей абсолютно субъективной и неавторитетной точки зрения\) новые функции C\#\.

Также стоит отметить любопытный момент\. Существует стандарт [ECMA\-334](https://ecma-international.org/publications-and-standards/standards/ecma-334), описывающий полную спецификацию C\#\. На момент написания статьи последняя опубликованная версия — седьмая\. И в ней нет, например, ключевого слова [`and`](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/operators/patterns#logical-patterns)\. А слово это появилось аж в C\# 9, т\.е\. четыре версии назад\. Более того, в официальном [репозитории](https://github.com/dotnet/csharpstandard), где ведётся разработка стандарта, есть две ветки: [draft\-v8](https://github.com/dotnet/csharpstandard/tree/draft-v8) и [draft\-v9](https://github.com/dotnet/csharpstandard/tree/draft-v9)\. И даже в черновике девятой версии в [файле](https://github.com/dotnet/csharpstandard/blob/draft-v9/standard/grammar.md) с описанием грамматики языка отсутствует тот самый `and`\. То есть, официальная спецификация и даже черновики её новых редакций [не поспевают](https://github.com/dotnet/csharpstandard/issues/1390) за фактическим развитием C\#\.

## Когда остановиться невозможно

Посмотрим, например, на [локальные функции](https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/local-functions), добавленные в [C\# 7\.0](https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-version-history#c-version-70)\. Выглядят как обычные методы, разве что без модификаторов доступа\. Значит, наверное, можно и статическими их делать? С [восьмой](https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-version-history#c-version-80) версии можно, — говорит Microsoft\. А ещё в сигнатурах методов есть возможность использовать атрибуты на параметрах или возвращаемом значении\. Как там дела с локальными функциями? Вот вам [C\# 9](https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-version-history#c-version-9) — пожелание реализовано, — отвечает Microsoft\.

Но разработчики языка пошли дальше\. По их мнению, пространства имён и метод `Main` так сильно пугают новичков, что те бегут изучать Python \(информация непроверенная\), а потому в той же девятой версии стало возможным отбросить эти ужасы\. [Top\-level statements](https://learn.microsoft.com/en-us/dotnet/csharp/fundamentals/program-structure/top-level-statements) позволили писать код так, будто мы находимся внутри `Main`, существующего где\-то там\. И это правда, ибо фича не более чем синтаксический сахар\. Объявлять методы при таком подходе помогают именно введённые ранее локальные функции\.

Кстати, лямбда\-функции ведь очень похожи на локальные\. Обладают некоторыми неудобствами по сравнению с последними, но всё же\. В локальных функциях можно объявлять опциональные параметры и использовать атрибуты, так почему в лямбда\-функциях этого делать нельзя? C\# версий [10](https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-version-history#c-version-10) и [12](https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-version-history#c-version-12) исправляет эти упущения\.

Или рассмотрим ключевое слово `ref`, позволяющее передать в метод объект по ссылке\. И снова [C\# 7\.0](https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-version-history#c-version-70) расширяет горизонты дозволенного\. Теперь можно [объявлять переменные через `ref`](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/statements/declarations#reference-variables) \(создавая псевдоним для другой переменной\) и даже [возвращать значение по ссылке](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/statements/jump-statements#ref-returns)\. Дальше — больше: [`ref readonly`](https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-12#ref-readonly-parameters), [`ref struct`](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/builtin-types/ref-struct), [`ref-поля`](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/builtin-types/ref-struct#ref-fields), `scoped ref`\.  При этом нужно ещё научить компилятор [не ругаться](https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-version-history#c-version-13) на использование тех же ref\-структур в дженериках или попытки реализовать такими структурами интерфейсы\.

Обширный пласт функциональности связан с записями \([`records`](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/builtin-types/record)\)\. Изначально ключевое слово `record` позволяло быстро объявить класс с набором свойств, причём всякую обвязку вроде `Equals` и `ToString` компилятор генерировал сам\. А что, если хочется сделать такой тип значимым \(`value type`\), а не ссылочным? Встречайте — `record struct`\. Связка этих ключевых слов \(а заодно и `record class` в качестве псевдонима для `record`\) появилась в [C\# 10](https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-version-history#c-version-10) и повлекла за собой сопряжение с модификатором `readonly`, применимым с [C\# 7\.2](https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-version-history#c-version-72) к обычным структурам\. В итоге сейчас можно объявлять записи такими способами:

```cpp
record A();
record class B();
record struct C();
readonly record struct D();
```

На [dev\.to](http://dev.to/), к слову, есть [статья](https://dev.to/waelhabbal/records-in-c-a-deep-dive-46hi), в которой приведён такой код:

```cpp
public ref readonly record struct Point(double X, double Y);
```

Думается, что автором является его величество ИИ, ибо скомпилировать такую конструкцию не удастся\. Хотя её наличие отлично бы вписалось в тенденцию, показанную выше: `readonly ref struct` ведь существует\.

При этом в записи можно добавлять методы и свойства, переопределять их и т\.д\. Иными словами, очень похоже на обычные классы и структуры\. Вот только последние не поддерживают тот замечательный синтаксис, в котором имя типа сочетается с конструктором\. Здесь история C\# приводит нас к концепции [первичных конструкторов](https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-12#primary-constructors) \(primary constructors\), появившихся в [12\-ой](https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-version-history#c-version-12) версии языка\. К слову, данная [фича](https://github.com/dotnet/csharplang/issues/2691) находится в лидерах по количеству отрицательных реакций — 186 голосов "за" и 96 "против"\.

<details>
   <summary>Как подсчитаны голоса</summary>

1. Взяты документы с предложениями новых фич из папки [proposals](https://github.com/dotnet/csharplang/tree/main/proposals) в репозитории [dotnet/csharplang](https://github.com/dotnet/csharplang) \(файлы в подпапках csharp\-<version\>\)\.
1. Внутри файлов найдены ссылки на champion issues, в рамках которых велись обсуждения нововведений\.
1. В каждом предложении\-чемпионе подсчитаны реакции пользователей \(_\+1_, _heart_ и _hooray_ — голоса "за", _\-1_ — голоса "против"\)\. Разумеется, когда один человек поставил, например, и _\+1_ и _heart_, прибавится только один голос "за"\.
1. На основе реакций для каждого issue вычислены два параметра: like ratio \(отношение числа положительных голосов к общему числу голосов\) и dislike ratio \(отношение числа отрицательных голосов к общему числу голосов\)\. После сортировки предложения получены два списка по убыванию значения каждого параметра\.
1. Также параллельно созданы два набора, где предложения упорядочены по убыванию абсолютного числа голосов "за" и "против" соответственно\.
1. После сопоставления соответствующих списков друг с другом получены фичи\-лидеры и фичи\-аутсайдеры\.
1. Метод не суперточный, но другого я не придумал\. Тем более неизвестно, какие ещё эмодзи могут использоваться людьми, и какой смысл они в них вкладывают\. Но это мелочи, не сильно меняющие позиции в рейтингах\.


</details>


Тем не менее, всё это — примеры полезных нововведений\. Локальные функции и записи я и сам время от времени использую\. Разве что top\-level statements полюбить не смог, и, кажется, я не один такой, учитывая, что Microsoft даже добавила галочку _Do not use top\-level statements_ при создании нового проекта в Visual Studio\.

## Когда нужно больше ключевых слов

Интересная история приключилась с [native sized integers](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/proposals/csharp-9.0/native-integers)\. Введённые в [C\# 9](https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-version-history#c-version-9) ключевые слова `nint` и `nuint` представляли собой "улучшенные" версии `IntPtr` и `UIntPtr` соответственно\. Улучшение заключалось в поддержке арифметики и неявных приведениях между числовыми типами\. Иными словами, вот такой код не скомпилировался бы:

```cpp
IntPtr a = 2;
IntPtr b = 3;
var c = a + b;
```

И такой тоже:

```cpp
IntPtr a = (IntPtr)2;
IntPtr b = (IntPtr)3;
var c = a + b;
```

А с `nint` — пожалуйста:

```cpp
nint a = 2;
nint b = 3;
var c = a + b;
```

Но в [C\# 11](https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-version-history#c-version-11) в Microsoft решили добавить все эти возможности и в давно живущие среди нас `IntPtr` и `UIntPtr`\. А если нет разницы, то пусть `nint` и `nuint` будут, в конце концов, просто [псевдонимами](https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-11#numeric-intptr-and-uintptr) для старых типов\. И вот мы имеем два ключевых слова, которые не добавляют никакой пользы\.

Забавный факт: выглядят они как обычные псевдонимы простых типов \(вроде `int` или `byte`\), но объявить переменную с именем `int` вы не сможете, а с `nint` — запросто:

```cpp
nint nint = 4;
```

Так получилось потому, что `nint` и `nuint` — это [контекстные ключевые слова](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/#contextual-keywords)\. Кроме того, если не знать их историю, можно подумать, а чего именно `IntPtr` и `UIntPtr` удостоились чести иметь псевдонимы? Почему, например, не `TimeSpan`? Думается, в жизни большинства разработчиков он встречается даже чаще\.

Есть ещё один пример внезапного пополнения списка ключевых слов\. И даже не двумя, а сразу тремя\. Речь про [сопоставление с образцом](https://learn.microsoft.com/en-us/dotnet/csharp/fundamentals/functional/pattern-matching) \(pattern matching, aka сопоставление шаблонов\)\. Фича безусловно полезная, значительно упрощающая жизнь\. Будучи добавленным в [C\# 7\.0](https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-version-history#c-version-70), паттерн матчинг пережил множество расширений и сейчас представляет собой очень мощный инструмент\.

Эволюция сего функционала привела к появлению в [C\# 9](https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-version-history#c-version-9) [логических паттернов](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/operators/patterns#logical-patterns) \(logical patterns\), а вместе с ними и новых ключевых слов: `not`, `and` и `or` \(на всякий случай оставлю [ссылку](https://github.com/dotnet/csharplang/issues/1350#issuecomment-388638887) на сообщение, в котором рассказывается, почему нельзя обойтись привычными `!`, `&&` и `||`\)\. Кроме того, сопоставление с образцом доступно не только в конструкциях `switch`, но и в `if` посредством использования оператора `is`\.

И хотя логика добавления новых операторов понятна, есть ощущение, что они лишние\. При этом вариант лучше придумать не могу\. Критикую и не предлагаю\.

## Когда догадаться сложно

Временами Microsoft благоразумно старается избежать добавления новых ключевых слов и переиспользует существующие\. Не всегда результат такой заботы радует глаз\.

Начало было положено задолго до обозначенной нами точки отсчёта\. Думаю, мало программистов, кому довелось впервые увидеть модификатор доступа [`protected internal`](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/protected-internal), догадывались, что союз между этими двумя словами вовсе не "и", а "или"\. Кажется вполне разумным читать это как "_можно обращаться только из производного класса в рамках текущей сборки \(assembly\)_"\. Но реальность жестока\. Правильная интерпретация: "_Можно обращаться из текущей сборки \(из любого класса\) или из производного класса \(из любой сборки\)_"\.

Когнитивный диссонанс и потребность в модификаторе доступа, соответствующем логичной мысли выше, привели к появлению не менее странного [private protected](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/private-protected) в [C\# 7\.2](https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-version-history#c-version-72)\. Кто знает, возможно, в будущем мы увидим появление, например, private protected internal \(пусть интерпретация будет такая: "_Можно обращаться только из производного класса в рамках текущего пространства имён_"\)\.

Ещё одним интересным объектом для рассмотрения является `default`\. Давным\-давно введённый оператор позволяет получать значение по умолчанию для любого типа\. Особенно удобно это для, например, дженериков:

```cpp
public T Foo<T>()
{
    // ...
    return default(T);
}
```

[C\# 7\.1](https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-version-history#c-version-71) упрощает оператор до [литерала](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/operators/default#default-literal):

```cpp
public T Foo<T>()
{
    // ...
    return default;
}
```

При этом издревле конструктор без параметров назывался конструктором по умолчанию \(default constructor\)\. И там, и там слово `default`, выходит, и `default` и `new T()` должны давать нечто одинаковое\. Или нет?

Объявим структуру, представляющую число, которое не может быть меньше 10 \(такие у нас специфические желания\):

```cpp
private struct A
{
    private int _x = 10;

    public A(int x)
    {
        if (x < 10)
            throw new ArgumentException("X is too small.", nameof(x));

        _x = x;
    }

    public override string ToString() => _x.ToString();
}
```

Далее создадим дефолтный экземпляр этого типа разными способами:

```cpp
A a1 = default(A);
Console.WriteLine($"A1 = {a1}");

A a2 = default;
Console.WriteLine($"A2 = {a2}");

A a3 = new();
Console.WriteLine($"A3 = {a3}");

A a4 = Activator.CreateInstance<A>();
Console.WriteLine($"A4 = {a4}");
```

В консоли будут напечатаны такие строки:

```cpp
A1 = 0
A2 = 0
A3 = 0
A4 = 0
```

Здесь перед нами предстаёт интересная особенность оператора и литерала `default`: он игнорирует инициализацию полей \(на что мне обратили внимание в комментариях к другой моей [статье](https://habr.com/ru/articles/831960)\)\. И хотя, как и в примере с `protected internal`, мы можем \(и должны\) обратиться к [документации](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/builtin-types/default-values), с первого взгляда поведение кажется неочевидным\.

Начиная с [C\# 10](https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-version-history#c-version-10) можно добавить в структуру конструктор без параметров, и вывод консоли поменяется:

```cpp
A1 = 0
A2 = 0
A3 = 10
A4 = 10
```

Такое поведение вызвало споры, а [фича](https://github.com/dotnet/csharplang/issues/99) попала на верхние строчки анти\-рейтинга нововведений — 40 положительных голосов и 29 отрицательных\.

Выходит, что слово `default` не совсем применимо одновременно и к соответствующему оператору/литералу, и к `new T()`\. В Microsoft тоже это поняли, так что в статье [Use constructors](https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/using-constructors) начиная с [20 апреля 2019](https://github.com/dotnet/docs/blob/1828bf989338ffa3fa2af8c817c914c0cd32ace6/docs/csharp/programming-guide/classes-and-structs/using-constructors.md) компания не использует термин _конструктор по умолчанию_ — теперь это _конструктор без параметров_ \(parameterless constructor\)\. Последняя версия текста, где фигурирует словосочетание _default constructor_, датируется [28 февраля 2019](https://github.com/dotnet/docs/blob/d5f4a01db281ae2c5749219c0228714621f5b533/docs/csharp/programming-guide/classes-and-structs/using-constructors.md)\.

## Заключение

Конечно, есть ещё, на что побрюзжать\. Например, на возможность реализовывать методы прямо в интерфейсе \([default interface methods](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/interface#default-interface-members)\), появившуюся в [C\# 8](https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-version-history#c-version-80) и вызвавшую [возмущение](https://github.com/dotnet/csharplang/issues/52) — 89 дизлайков против 142 голосов "за"\. Но, перефразируя Маяковского: если фичи добавляют — значит — это кому\-нибудь нужно? Без сомнений\.

Язык расширяется, а разработчики получают возможность решать свои задачи чуточку \(а иногда и ого\-го как\) быстрее и проще\. Но всё\-таки иногда возникает ощущение утраченной стройности — чего\-то, что делало язык одновременно простым и мощным\.