Asp net core 6 что нового
Вместе с Visual Studio 2022 Preview 1 17 июня вышла новая Preview-версия .NET 6 — .NET 6 Preview 5 c с обновлениями для ASP.NET Core, Entity Framework Core и .NET MAUI.
Чтобы использовать .NET 6 Preview 5 c обновлениями ASP.NET Core, Entity Framework Core и .NET MAUI, можно установить новую среду Visual Studio 2022 Preview 1 , которая уже по умолчанию включает эти компоненты. Либо использовать Visual Studio 2019 version 16.11 Preview 2 и Visual Studio for Mac 8.9.
Рассмотрим вкратце некоторые основные изменения в .NET 6 Preview 5, ASP.NET Core, Entity Framework Core и .NET MAUI
Что нового в .NET 6 Preview 5?
- Добавленая новая функциональность — SDK workloads (Рабочие нагрузки SDK), которая позволяет добавлять поддержку для новых типов приложений без увеличения размера SDK. Эта функциональность поддерживает две команды list и update :
- dotnet workload list позволяет вывести список установленных рабочих нагрузок позволяет вывести список установленных рабочих нагрузок
using HostBuilder host = new() .ConfigureHostOptions(o => < o.ShutdownTimeout = TimeSpan.FromMinutes(10); >) .Build(); host.Run();
await using (var scope = provider.CreateAsyncScope()) < var foo = scope.ServiceProvider.GetRequiredService(); >
var cws = new ClientWebSocket(); cws.Options.DangerousDeflateOptions = new WebSocketDeflateOptions() < ClientMaxWindowBits = 10, ServerMaxWindowBits = 10 >;
var cws = new ClientWebSocket(); var handler = new HttpClientHandler < Proxy = new WebProxy("socks5://127.0.0.1", 9050) >; var httpClient = new HttpClient(handler);
Подобнее про новвоведения в .NET 6 Preview 5 можно прочитать в официальном блоге Microsoft на странице https://devblogs.microsoft.com/dotnet/announcing-net-6-preview-5/
Что нового в ASP.NET Core в .NET 6 Preview 5
- Обновлен функционал команды dotnet watch в .NET Hot Reload
- Шаблоны проектов ASP.NET Core SPA обновлены до версий Angular 11 и React 17 (прим. ред.: месяц назад вышел Angular 12)
- В SVG-элементах foreignObject можно использовать синтаксис Razor
@message
@code
app.Use(async (context, next) => < var hostHeader = context.Request.Headers.Host; app.Logger.LogInformation("Host header: ", hostHeader); context.Response.Headers.XPoweredBy = "ASP.NET Core 6.0-preview5"; await next.Invoke(context); var dateHeader = context.Response.Headers.Date; app.Logger.LogInformation("Response date: ", dateHeader); >);
Более подробно про новвоведения ASP.NET Core в NET 6 Preview 1 в блоге Microsoft: https://devblogs.microsoft.com/aspnet/asp-net-core-updates-in-net-6-preview-5/
Что нового в Entity Framework Core в .NET 6 Preview 1
В Entity Framework Core в .NET 6 Preview 1 добавлена новая функциональность — компилируемые модели (compiled models). Они значительно увеличили производитльность. Так, время запуска приложения по секам Microsoft уменьшилось чуть ли не в 10 раз. По мере того, как компилируемые модели увеличиваются за счет новых свойств и связей, начальное время запуска остается постоянным
В то же время компилируемые модели поддерживают не все возможности, доступные для стандартных моделей.
Что нового в .NET MAUI Preview 5
- Добавлено новое событие AttachedHandler для управления прикрепленными визуальными элементами
- Для некоторых элементов управления свойства и события портированы из архитектуры рендереров Xamarin.Forms в обработчики событий
- Обновлен шаблон проекта — теперь он включает два проекта: собственно проект .NET MAUI, который нацелен на разные системы, и проект WinUI. Для поддержи WinUI в настоящий момент необходимо установить расширение Project Reunion 0.8 (Preview)
ASP.NET Core in .NET 8 Preview 6: Blazor Updates and More
The latest release of .NET 8 Preview 6 brings significant additions and changes to ASP.NET Core. The most notable enhancements for this release of ASP.NET Core are related to the Blazor alongside the updates regarding the debugging experience, testing metrics, API authoring, servers, middleware and many more.
Regarding the Blazor, several new features have been introduced to enhance its server-side rendering mode. One notable addition is the ability to model bind and validate HTTP form post values. This can be achieved by applying the SupplyParameterFromForm attribute to a component property, enabling easier data binding from form requests.
Moreover, Blazor now offers enhanced page navigation and form-handling capabilities. By intercepting requests and applying the response to the existing DOM, the framework preserves as much of the page as possible, resulting in a smoother user experience to a single-page app (SPA) while still utilizing server-side rendering.
Blazor’s streaming rendering has been enhanced to preserve existing DOM elements during updates, leading to faster and smoother interactions. Developers now have the ability to specify the render mode for a component instance using the @rendermode directive attribute, granting more precise control over rendering behaviour. Enabling call site @rendermode usage requires setting the Razor Language Version to 8.0 in the project file, though this step will be automated in future framework releases, eliminating the need for manual adjustments.
Blazor WebAssembly now supports interactive rendering of components, although this option is not yet exposed in the Blazor Web App template. Interested developers can enable this functionality manually to take advantage of the interactive rendering capabilities offered by Blazor WebAssembly. A demo sample is provided to illustrate the setup of WebAssembly-based interactivity for a Counter component rendered within the Index page. This sample serves as a practical example of implementing interactivity using WebAssembly in Blazor applications.
There were a couple of improvements to how Blazor sections interact with other Blazor features. These enhancements include changes to how cascading values are handled, with values now flowing into section content from their point of definition rather than where they are rendered in a section outlet. Additionally, unhandled exceptions are now handled by error boundaries around the section content, rather than the section outlet. Furthermore, the decision on whether section content should use streaming rendering is now determined by the component where the section content is defined, rather than the component defining the section outlet.
Other significant Blazor-related changes include the introduction of cascading query string values to Blazor components, the incorporation of a Blazor Web App template option for enabling server interactivity, and template consolidation.
From Preview 6 developers can now benefit from improved testing of metrics in their applications. The introduction of ASP.NET Core metrics in a previous .NET 8 preview has made it easier to test metrics, thanks to the IMeterFactory API. This API integrates metrics with dependency injection, simplifying the process of isolating and collecting metrics for testing purposes. Whether conducting unit tests or running multiple tests simultaneously, developers can rely on IMeterFactory to gather accurate data specifically tailored to their testing needs.
ASP.NET Core reinforces its commitment to metrics support with the introduction of new, improved, and renamed counters. These enhancements aim to enhance observability in reporting dashboards and enable custom alerts for ASP.NET Core applications. The new counters include metrics for routing success and failure, exception handling, unhandled requests, and rate limiting. Improved counters now provide additional details on connection duration and transport types
Other notable changes included in this release are related to complex form binding support in minimal APIs, HTTP.sys kernel response buffering, Redis-based output caching and improved debugging experience for the Web Application type. The team recommends the installation of the latest Visual Studio 2022 Preview and there is also a full list of breaking changes in ASP.NET Core for .NET 8 so developers can check that out along the way.
Lastly, the comment section on the original release blog post has been full of reactions, regarding the changes and improvements for Blazor and the blog post has generated significant activity, with users engaging in numerous questions and discussions with the development team. To get a comprehensive understanding of the various perspectives, it is highly recommended for users explore the comment section and engage in the ongoing discussion.
Обновления ASP.NET Core в .NET 6 Preview 1
Новая версия .NET, 6 Preview 1, уже доступна и готова к вашей оценке. Это первая предварительная версия .NET 6, следующего крупного обновления платформы .NET. Ожидается, что .NET 6 поступит в полноценный доступ в ноябре этого года и будет выпуском с долгосрочной поддержкой (LTS).
Если вы работаете с Windows и используете Visual Studio, мы рекомендуем установить последнюю предварительную версию Visual Studio 2019 16.9. Если вы используете macOS, мы рекомендуем установить последнюю предварительную версию Visual Studio 2019 для Mac 8.9.
Основная работа, запланированная с ASP.NET Core в .NET 6
.NET 6 использует открытый процесс планирования, поэтому вы можете изучить все основные темы, запланированные для этого релиза, на Blazor-веб-сайте themesof.net. В дополнение к этим верхнеуровневым темам мы собираемся также предоставить множество улучшений, ориентированных на пользователей. Вы можете найти список основных задач, запланированных для ASP.NET Core в .NET 6, в нашем выпуске дорожной карты. Вот некоторые из основных функций ASP.NET Core, запланированных для выпуска .NET 6:
- Горячая перезагрузка: быстро обновляйте пользовательский интерфейс и код для работающих приложений без потери состояния приложения для более быстрой и продуктивной разработки.
- Микро API: Упростите создание конечных точек API с гораздо меньшим количеством кода и церемоний.
- Публикация в виде одного файла: создавайте небольшие автономные высокопроизводительные приложения и службы.
- Компиляция WebAssembly с опережением времени (AoT): компилируйте код .NET в приложениях Blazor WebAssembly непосредственно в WebAssembly при публикации для значительного повышения производительности во время выполнения.
- Обновленная поддержка одностраничных приложений (SPA). Обновите интеграцию SPA в ASP.NET Core для бесперебойной работы с последними современными интерфейсными платформами JavaScript.
- Гибридные настольные приложения Blazor: объедините лучшее из пользовательского интерфейса многоплатформенных приложений Blazor и .NET для создания кроссплатформенных гибридных настольных приложений.
- HTTP/3: добавьте поддержку HTTP/3 и QUIC на поддерживаемые серверы ASP.NET Core.
Мы приветствуем отзывы и участие в процессе планирования и создания на GitHub.
Что нового в ASP.NET Core в .NET 6 Preview 1?
- Поддержка IAsyncDisposable в MVC
- DynamicComponent
- Input ElementReference разделен на релевантные компоненты
- dotnet watch теперь является dotnet watch run по дефолту
- Nullable reference type annotations
Начало работы
Чтобы начать работу с ASP.NET Core в .NET 6 Preview 1, установите .NET 6 SDK.
Обновление существующего проекта
Чтобы обновить существующее приложение ASP.NET Core с .NET 5 до .NET 6 Preview 1:
- Обновите целевую платформу для вашего приложения, до net6.0 .
- Обновите все ссылки на пакеты Microsoft.AspNetCore.* до 6.0.0-preview.1.* .
- Обновите все ссылки на пакеты Microsoft.Extensions.* до 6.0.0-preview.1.* .
См. полный список критических изменений в ASP.NET Core для .NET 6 здесь.
DynamicComponent
DynamicComponent — это новый встроенный компонент Blazor, который можно использовать для динамической визуализации компонента, указанного по типу.
Параметры могут быть переданы визуализируемому компоненту с помощью dictionary:
@code < Type someType = . IDictionarymyDictionaryOfParameters = . >
InputElementReference разделен на релевантные компоненты
Соответствующие встроенные компоненты Blazor ввода теперь предоставляют удобную ссылку ElementReference для базового ввода, что упрощает распространенные сценарии, такие как установка фокуса пользовательского интерфейса на вводе. Затронутые компоненты: InputCheckbox, InputDate, InputFile, InputNumber, InputSelect, InputText и InputTextArea.
dotnet watch теперь является dotnet watch run по дефолту
Запуск dotnet watch теперь будет запускать dotnet watch run по умолчанию, экономя драгоценное время ввода.
Nullable Reference Type Annotations
Мы применяем аннотации обнуляемости к частям ASP.NET Core. Значительное количество новых API было аннотировано в .NET 6 Preview 1.
Используя новую функцию C# 8, ASP.NET Core может обеспечить дополнительную безопасность во время компиляции при обработке ссылочных типов, например защиту от исключений нулевых ссылок. Проекты, которые выбрали использование аннотаций, допускающих значение NULL, могут видеть новые предупреждения во время сборки от API-интерфейсов ASP.NET Core.
Чтобы включить ссылочные типы, допускающие значение NULL, вы можете добавить в файл проекта следующее свойство:
enable Что нового в .NET 6?
На момент написания этих строк вышло уже семь превью-версий .NET 6. Дальше — только релиз-кандидаты. Все основные фичи уже добавлены во фреймворк, идёт отладка, тестирование и оптимизация. Ожидать чего-то кардинально нового в RC-версиях, пожалуй, уже не стоит. Пришла пора рассмотреть .NET 6 поближе.
Пресс-релиз для каждой версии содержит огромное количество восхвалений и убеждений в том, что теперь-то всем станет ещё лучше, и как мы вообще жили раньше — уму непостижимо. Где-то авторы не врут, где-то не договаривают, где-то преувеличивают. Пришлось тщательно прочитать все семь пресс-релизов, изучить массу смежных материалов и просмотреть огромное количество тикетов на Гитхабе. Всё для того, чтобы понять, чем они там занимаются и что выкатывают нам посмотреть.
Поговорим об этом.
Производительность
Разработчики .NET всегда делали упор на производительность. С одной стороны, в язык и фреймворк постоянно добавляются новый функционал — ref struct , stackalloc , System.Span и всё такое прочее. С другой стороны, с каждой новой версией .NET добавляются новые оптимизации — многопроходная (tiered) компиляция, компиляция в нативный код и, разумеется, огромное количество оптимизаций, которые делает JIT-компилятор. Грамотное использование этих средств даёт свой эффект, который хорошо видно в реальных боевых условиях на графиках производительности.
В NET 6 представлены три инструмента, которые дают ещё большие возможности для повышения эффективности. Причём, не только для самих приложений, работающих в продакшне, но и для разработчиков. Речь идёт о прокачаной предварительной компиляции (через утилиту Crossgen2), оптимизации на основе профилирования (PGO) и горячей перезагрузке приложений во время отладки.
Строго говоря, некоторые из представленных инструментов — это кардинальная переработка уже существующих. Но эта переработка открывает массу новых интересных возможностей.
Предварительная компиляция
Как известно, преимущества JIT-компиляции имеют свою цену. В частности, повышенное время «прогрева» приложения во время старта, поскольку JIT-компилятору требуется перемолоть разом слишком много IL-кода. Эту проблему уже пытались решить компиляцией приложений сразу в нативный код, такая технология уже есть и называется Ready To Run. Но в новой версии фреймворка её значительно переработали.
Справка: предварительной компиляцией в этой статье называется аббревиатура AOT (Ahead Of Time), используемая в англоязычных источниках.
Старая технология предварительной компиляции была слишком примитивна и позволяла только генерировать нативный код для той платформы, на которой была запущена старая утилита crossgen. Разработчики полностью переписали её с нуля на управляемом коде и назвали Crossgen2. Теперь она предоставляет новые возможности: авторы делают упор на оптимизации, а также использование различных стратегий компиляции для разных платформ (Windows/Linux/macOS/x64/Arm). Всё это достигается новой архитектурой утилиты.
Вкратце это работает так: Crossgen2 разбирает IL-код, составляя некий граф приложения. Затем он запускет внутри себя JIT-компилятор для необходимой платформы, а этот компилятор, анализируя составленный граф, уже создаёт нативный код, применяя при необходимости различные оптимизации. Другими словами, утилита Crossgen2 может быть запущена на платформе x64, но она сгенерирует нативный и даже оптимизтированный код для Arm64. И, разумеется, наоборот это тоже работает.
В настоящий момент код .NET SDK скомпилирован уже с помощью Crossgen2, а старая утилита crossgen отправлена на пенсию.
Оптимизация на основе профилирования
Ещё одна новая старая фишка в .NET 6 — это Profile-Guided Optimization (PGO). Ни для кого не секрет, что обычно в приложении никогда не исполняется вообще весь написанный код. Какой-то код работает чаще других, какой-то вызывается в крайне редких случаях, а какой-то вообще никогда. Но компилятор обычно ничего об этом не знает, а лучше бы знал. Чтобы научить этому компилятор используется PGO-оптимизация. Её смысл заключается в том, что приложение просто прогоняется на разных стандартных кейсах, а заодно профилируется. Итоги профилирования анализируются компилятором, и он начинает распознавать самые часто используемые места кода, уделяя им особое внимание и оптимизируя их более тщательно.
Такое обучение компилятора похоже на обучение нейронной сети. К слову, в некоторых других распространённых языках программирования технология PGO реализована уже давно, но в .NET до этого добрались только сейчас. Эта тема довольно замороченная, и ребята занялись ей очень серьёзно, реализовав несколько различных подходов к компиляции итогового нативного кода.
Один из подходов — разделение на часто и редко используемый код (hot-cold splitting). Те части кода, которые используются наиболее часто (hot code), группируются и помещаются рядом в итоговом бинарнике. Если сильно повезёт, то такой сгруппированный код полностью поместится в кеш процессора, и вызовы различных часто используемых методов будут практически бесплатными и очень быстрыми. Напротив, некий крайне редко используемый код (very cold code) может вообще не быть скомпилирован в нативный. Например, else -ветки, в которых просто выбрасывается исключение. Такой код остаётся в виде IL-кода и будет скомпилирован в нативный уже после запуска приложения и только в том случае, если это будет необходимо. Такое разделение позволяет не только добиться более высокой производительности при старте, но и генерировать бинарники меньшего размера.
Другой подход — динамическая PGO. То есть, все этапы предварительного обучения JIT-компилятора пропускаются, а вместо этого он внимательно смотрит на то, как приложение работает в реальной среде и при необходимости заново компилирует какой-либо участок кода в более оптимальный. Если вы помните, то подобная технология уже существует — это многопроходная (tiered) компиляция (упоминается в начале статьи). Но разработчики JIT-компилятора просто серьёзно её прокачали.
Третий подход — сочетание динамического и статического профилирования для генерации нативного кода. Обе эти техники реализуются одновременно, и итоговый бинарник может частично содержать нативный код для быстрого запуска, который затем может быть оптимизирован ещё больше. В качестве примера приводится ситуация, когда некий интерфейс в программе реализован только в одном классе. В этом случае JIT-компилятор может девиртуализировать вызовы методов интерфейса и сгенерировать код для прямых вызовов методов класса, а также допустить встраивание этих методов прямо в код (inlining), если это будет необходимо.
Техника PGO работает в тесной связке с утилитой Crossgen2 и позволяет генерировать оптимизированный нативный код, а также экономить на размере итоговых бинарников. Но нужно отдавать себе отчёт в том, что статическая PGO — это довольно сложно для обычного разработчика. Ведь ему придётся заниматься многократным профилированием своего кода, результаты которого (а это очень много информации) нужно будет специальным образом подавать на вход при компиляции через Crossgen2. И хорошо, если результаты профилирования в тестовой среде будут пригодны и для продуктивной среды — тогда итоговый профит получить можно. Скажем, приложение будет гораздо быстрее запускаться и прогреваться. Это важный фактор, но надо помнить, что цена такой оптимизации — ресурсы, затраченные на предварительное профилирование, которое должно быть проведено очень аккуратно. Если при прогоне приложения на тестовой среде вы сделаете упор на редкие кейсы (например, тестировщики будут прогонять только негативные сценарии, пытаясь всё сломать), то данные профилирования у вас будут сильно отличаться от боевых. А значит, в итоговом бинарнике у вас предкомпилированным и оптимизированным может оказаться вообще не тот код.
Но, слава богу, есть ещё динамическая PGO, которая не требует предварительных ресурсозатрат и может повышать производительность вашего приложения прямо на лету, причём, делать это более эффективно, чем уже реализовано в существующей многопроходной компиляции. Правда, как вы понимаете, каждый новый рестарт приложения — это всякий раз оптимизация заново.
В общем, выбор у вас есть. Делайте его по ситуации.
Горячая перезагрузка приложений
Эта новая возможность действительно впечатляет. Любому разработчику хочется при отладке быстро пофиксить какой-то мелкий кусок кода без последующей перезагрузки приложения и прохождения заново всего пути к месту отладки. Такая возможность была и раньше, но в очень сильно упрощённом варианте и только в мощной IDE, вроде Visual Studio. Теперь же её прокачали настолько, что она реально позволит сэкономить уйму времени, избавившись от постоянных действий остановка-правка-ребилд-деплой-запуск-достижение точки отладки, причём, в любой IDE, даже в VS Code.
Это работает ещё интереснее, чем вы можете себе представить. Не нужно устанавливать брейкпойнт или ставить приложение на паузу во время отладки. Достаточно просто внести изменения в код и применить их прямо к работающему приложению. В последних билдах Visual Studio это поддерживается легко и просто:
Но даже если вы пользуетесь не студией, а VS Code, то вы не будете ущемлены. Вам нужно просто запустить ваш проект с помощью новой команды dotnet watch . После этого любые изменения в исходных файлах будут автоматически обнаружены, скомпилированы и подгружены в работающее приложение без каких-либо телодвижений с вашей стороны. Вы увидите изменения без его перезагрузки. Проще некуда, и это работает.
Совет: при использовании горячей перезагрузки лучше выключить автосохранение файлов при редактировании. Иначе вы можете случайно автосохранить неработающий код, который не сможет быть скомпилирован. Горячая перезагрузка при этом не перестанет работать, но будет ждать новых изменений файлов, а в это время ваше запущенное приложение уже будет поставлено на паузу.
В случаях посерьёзнее (например, при отладке приложений ASP.NET) вам придётся добавить настройку в launchSettings.json , разрешающую горячую перезагрузку, что вряд ли станет большой проблемой.
Ложкой дёгтя во всём этом является тот факт, что не всякое изменение можно применить с помощью горячей перезагрузки. Полный список тех действий, которые можно и нельзя перезагрузить по-горячему, можно увидеть здесь.
Ах, да: в F# горячая перезагрузка не поддерживается в принципе. Может, когда-нибудь позже. Просто попросите разработчиков об этом.
Более подробно о горячей перезагрузке написано в переводе на Хабре.
Прочие производительные плюшки
Кроме упомянутых трёх очень важных нововведений в обычном цикле разработки удалось найти массу других мест для оптимизации, ускорив тем самым процесс билда и запуска приложений: ликвидировали причины оверхедов, оптимизировали MSBuild, перевели Razor-компилятор на Roslyn source generator и даже позаботились о том, чтобы пореже трогать файлы и зря беспокоить антивирусное ПО.
В общем, производительность труда разработчиков должна вырасти. По крайней мере, на это намекает диаграмма снижения времени билдов.
Поддержка ОC и платформ
.NET 6 будет поддерживать ещё больше операционок и платформ. Полный список доступен по этой ссылке. Большое внимание уделяется платформе Arm64 в целом: улучшена поддержка Windows Arm64 и добавлена поддержка Arm64-чипов Apple. Что касается последних, то, как известно, эти чипы умеют работать как в нативном режиме, так и в режиме эмуляции x64. .NET 6 будет поддерживать оба режима и будет уметь создавать как обычный x64, так и нативный для Arm64 код. Для разработки под macOS теперь будут два типа проектов: net6.0-macos для x64 и net6.0-maccatalyst для Arm64 архитектур.
Полный список новых Target Framework’ов теперь выглядит так:
- net6.0
- net6.0-android
- net6.0-ios
- net6.0-maccatalyst
- net6.0-macos
- net6.0-tvos
- net6.0-windows
Однако, с программированием для Apple-устройств есть один нюанс: существует требование, которые предъявляется к приложениям, публикуемым в App Store. Если разработчик приложения хочет, чтобы приложение запускалось как на x64, так и на Arm64 архитектурах, то оно должно быть скомпилировано как Universal Binaries. Вот с этим требованием пока всё плохо: оно просто не поддерживается в .NET 6. В следующей версии .NET 7 разработчики посмотрят, что можно сделать. Впрочем, это не самое критичное требование, пока можно прожить и без него.
В общем, теперь можно брать новые Макбуки.
Также .NET 6 теперь существует для нескольких новых Linux-дистрибутивов: Alpine 3.13, Debian 11 и Ubuntu 20.04 — соответствующие docker-образы создаются с первого превью .NET 6.
Унификация, поглощение Xamarin, «optional workloads» и MAUI
Ещё пару лет назад разработчики .NET объявили, что собираются объединить в одном .NET-флаконе разработку для всего сразу. Ну, то есть, ничего не будет, а будет одно сплошное телевидение один фреймворк для всего, что только есть на свете — и для мобильной, и для серверной, и для веб-разработки, и для IoT, и для… не знаю, что там ещё появится в будущем. И они назвали это .NET 5, перескочив, во-первых, через версию, чтобы не было путаницы с классическим .NET Framework 4.x, а во-вторых, объединив классический фреймворк с Core, к чему стремились с самого начала, просто осторожно шли окольными путями.
В качестве профита от такого объединения упоминались две ключевые фишки:
- вы пишете на одном языке с использованием одного API;
- вы не используете то, что вам не надо: новый фреймворк достаточно раздробленный, и вам не нужно устанавливать кучу ненужных библиотек.
Люди, знающие .NET, когда он ещё пешком под стол ходил, в этом месте начинали припоминать, что примерно такие же обещания раздавались налево и направо двадцать лет назад (а потом повторялись с появлением Silverlight и UWP). Классический фреймворк, вроде как, преследовал эти же самые цели, но только был неделимым, как атом, монолитом, заточенным под одну ОС. Однако, мир менялся быстрее и не в ту сторону. Но в MS вовремя опомнились и умудрились запрыгнуть в уходящий поезд, выпустив первую версию Core, да ещё и выведя разработку в Open Source.
Сейчас .NET далеко не в последнем вагоне этого поезда, и, похоже, тотальная унификация действительно не за горами.
Так вот. Пятую версию .NET выпустили, но унификация продолжается: добрались до Xamarin и поглотили его подружили его с .NET 6. Речь идёт, конечно же, о разработке под Android, iOS и macOS. Вообще, вы теперь и без Xamarin имеете возможность набрать команду dotnet new android и начать разрабатывать под Андроид. А запускать разработанное вы будете командой dotnet run . Но я попробовал — это не работает. Такого шаблона проекта даже нет в последней превью-версии .NET 6. Это потому, что соответствующие библиотеки для разработки под Андроид (а также iOS и macOS) — ну, то есть, то, что раньше было частью Xamarin — не являются частью стандартного .NET SDK. Их нужно скачивать отдельно. В первую очередь, это объясняется тем, что не хочется снова создать огромный монолит. В общем, всё постороннее, что пришло вместе с Xamarin, вынесено в «Optional SDK Workloads» — некие дополнительные части фреймворка, не входящие в стандартный SDK. Иначе размер SDK станет неприличным, а сам он начнёт противоречить одной из заявленных целей: не устанавливать кучу ненужного.
Вот этот вот новый «Optional SDK Workloads» теперь является частью .NET 6 и будет продолжать развиваться в .NET 7. Таким образом происходит слияние Xamarin с .NET. Но Xamarin в данном случае не только что-то отдаёт, но и получает взамен: разработка теперь будет вестись с использованием единой BCL, в едином стиле и с едиными подходами, а также можно будет использовать единую систему всех .NET-утилит, начиная с уже упомянутой dotnet new android . Разумеется, делается акцент и на сокращённом времени билда, уменьшении размеров итогового приложения, а таже улучшенной производительности.
Это ещё не всё, что происходит с Xamarin. Анонсировали новый .NET Multiplatform App UI (MAUI) — «эволюция» Xamarin Forms. С этого момента, думаю, про название «Xamarin Forms» можно уже начать забывать. Отныне вся кроссплатформенная UI-разработка будет называться MAUI. Разумеется, по своей сути MAUI — это мультиплатформенная абстракция над различными UI, родными для каждой конкретной платформы. На MAUI можно разработать интерфейсы, которые будут работать и на Blazor, и на мобильных платформах и даже в десктопных приложениях.
Также, скорее всего, можно начать забывать и про Mono, и про сам Xamarin. В шестой версии .NET они пока ещё живы как самостоятельные продукты, но есть подозрение, что седьмая поглотит их окончательно.
А пока разработчики на Xamarin получают возможность полноценно использовать родной .NET 6.0 SDK для кроссплатформенной мобильной разработки.
Как же теперь с этим всем работать, если не получается выполнить команду dotnet new android ? Ну, утилиту dotnet , вообще-то, доработали: для работы с «optional SDK workloads» теперь есть команда dotnet workload . Интересно, что она пока не выводится как доступная при вызове dotnet —help , но пользоваться уже можно:
>dotnet workload search android Workload ID Description ---------------------------------------------------------------- microsoft-android-sdk-full Android SDK maui-android .NET MAUI SDK for Android microsoft-net-runtime-android Android Mono Runtime microsoft-net-runtime-android-aot Android Mono AOT Workload
Никто не мешает вам уже сейчас загрузить нужный дополнительный SDK и попробовать написать небольшой «Hello World» для вашей мобилки. И даже, наверное, без установки Xamarin. Самое приятное: обещают, что можно будет работать с этим в VS Code, не надо будет ставить могучую и неповоротливую полноценную Студию. Желающие могут это сделать прямо сейчас, скачав готовые примеры из репозитория.
Ждём в .NET-разработку притока мобильщиков?
Опытные разработчики под iOS с интересом ждут выхода релиза .NET 6 и хотят посмотреть как будет выглядеть .NET-разработка под iOS без Apple-устройств и Xcode. Обещается, что с машин на Windows можно будет подключаться к устройствам Apple для отладки приложения в симуляторах. Посмотрим.
Blazor на десктопе
Оказывается, Blazor стал достаточно популярным (по заверениям разработчиков .NET), причём, настолько, что было решено сделать десктоп-версию Blazor-приложений. Модель разработки это позволяет.
В общем, теперь вы можете написать Blazor-приложение, которое запустится не только в браузере как WebAssembly, но и на Windows и macOS как нативное десктопное.
Улучшения в System.Text.Json
Вот и добрались до изменений в SDK. А их достаточно много, очень сложно пройти мимо. Начнём с System.Text.Json — эту библиотеку очень сильно прокачали.
Все примеры далее взяты из официальных пресс-релизов команды разработки .NET.
Игнор цикличных ссылок
В сериализатор добавили опцию игнорирования цикличных ссылок.
class Node < public string Description < get; set; >public object Next < get; set; >> void Test() < var node = new Node < Description = "Node 1" >; node.Next = node; var opts = new JsonSerializerOptions < ReferenceHandler = ReferenceHandler.IgnoreCycles >; string json = JsonSerializer.Serialize(node, opts); Console.WriteLine(json); // Prints >
Обратите внимание на то, что сериализатор заменяет ссылку на null , а не игнорирует свойство полностью.
Если честно, то сложно представить себе ситуацию, продемонстрированную в примере. Но будем иметь в виду.
Поддержка IAsyncEnumerable
Сериализатор System.Text.Json теперь поддерживает IAsyncEnumerable -объекты. При сериализации он их превращает в массивы:
using System; using System.Collections.Generic; using System.IO; using System.Text.Json; static async IAsyncEnumerable PrintNumbers(int n) < for (int i = 0; i < n; i++) yield return i; >using Stream stream = Console.OpenStandardOutput(); var data = new < Data = PrintNumbers(3) >; await JsonSerializer.SerializeAsync(stream, data); // prints
Для десериализации JSON-документов, которые представляют собой просто массив на корневом уровне, добавили новый удобный метод JsonSerializer.DeserializeAsyncEnumerable :
using System; using System.IO; using System.Text; using System.Text.Json; var stream = new MemoryStream(Encoding.UTF8.GetBytes("[0,1,2,3,4]")); await foreach (int item in JsonSerializer.DeserializeAsyncEnumerable(stream))
JSON DOM
Самое интересное нововведение — это возможность работать с JSON-документом как с DOM. Эта особенность довольно полезна, поскольку часто просто не хочется плодить POCO-объекты для простых операций. Вот пример того, как это теперь работает:
// Parse a JSON object JsonNode jNode = JsonNode.Parse(""); int value = (int)jNode["MyProperty"]; Debug.Assert(value == 42); // or value = jNode["MyProperty"].GetValue(); Debug.Assert(value == 42); // Parse a JSON array jNode = JsonNode.Parse("[10,11,12]"); value = (int)jNode[1]; Debug.Assert(value == 11); // or value = jNode[1].GetValue(); Debug.Assert(value == 11); // Create a new JsonObject using object initializers and array params var jObject = new JsonObject < ["MyChildObject"] = new JsonObject < ["MyProperty"] = "Hello", ["MyArray"] = new JsonArray(10, 11, 12) >>; // Obtain the JSON from the new JsonObject string json = jObject.ToJsonString(); Console.WriteLine(json); // > // Indexers for property names and array elements are supported and can be chained Debug.Assert(jObject["MyChildObject"]["MyArray"][1].GetValue() == 11);
До сих пор в подобных случаях нужно было пользоваться классами Utf8JsonWriter / Utf8JsonReader , но DOM-подход тоже неплох.
Надо отдавать себе отчёт в том, что DOM-подход к работе с JSON неизбежно ведёт к падению производительности и перерасходу ресурсов. Разработчики утверждают, что это не так, и что Writable DOM Feature на самом деле высокопроизводительна, но нас легко рассудят бенчмарки, которые обязательно кем-нибудь будут сделаны в ближайшем будущем.
Поддержка source generators для сериализации
В плане перерасхода ресурсов от DOM-модели не сильно отстаёт обычная сериализация и десериализация. Она основана на рефлексии, а это заведомо медленно. Поэтому там, где реально нужна производительность, всегда лучше было работать с . Writer и . Reader классами (это правило касается не только работы с JSON, но также и с XML). Такая работа занимает больше времени, но окупается максимальной производительностью на продакшне.
Однако разработчики .NET 6 и тут придумали обходной манёвр для облегчения жизни разработчиков: source generators. Эту новую технологию завезли в System.Text.Json , и она решает все основные проблемы, связанные с низкой производительностью обычных сериализаторов: уменьшает время старта приложения и количество используемой памяти, увеличивает скорость работы, не использует рефлексию. Что же тогда используется взамен, если не рефлексия? Именно тот самый класс Utf8JsonWriter , через который и происходит работа с JSON.
Выглядит такая техника точно так же, как и при любой другой работе с source generators. Сначала вы создаёте тип для сериализации/десериализации:
namespace Test < internal class JsonMessage < public string Message < get; set; >> >
Как видите, он слишком простой, но для иллюстрации работы этого достаточно. Затем вы создаёте partial -класс и сопровождаете его соответствующим атрибутом:
using System.Text.Json.Serialization; namespace Test < [JsonSerializable(typeof(JsonMessage)] internal partial class JsonContext : JsonSerializerContext < >>
После этого на этапе компиляции ваш частичный класс будет расширен несколькими методами и свойствами:
internal partial class JsonContext : JsonSerializerContext < public static JsonContext Default < get; >public JsonTypeInfo JsonMessage < get; >public JsonContext(JsonSerializerOptions options) < >public override JsonTypeInfo GetTypeInfo(Type type) => . ; >
Через одно из этих свойств — JsonMessage вы получите доступ к сгенерированному сериализатору, работа с которым будет выглядеть как-то так:
using MemoryStream ms = new(); using Utf8JsonWriter writer = new(ms); JsonContext.Default.JsonMessage.Serialize(writer, new JsonMessage < "Hello, world!" >); writer.Flush(); // Writer contains: //
Стандартный сериализатор также прокачан и может принимать на вход сгенерированный с помощью source generator код:
// Способ 1 JsonSerializer.Serialize(jsonMessage, JsonContext.Default.JsonMessage); // Способ 2 JsonSerializer.Serialize(jsonMessage, typeof(JsonMessage), JsonContext.Default);
Второй способ будет работать чуть-чуть медленнее, но всё равно такая сериализация через генерацию кода будет работать куда быстрее, чем старая сериализация через рефлексию.
Разумеется, сериализация через генерацию кода поддерживает не только примитивные типы, но и объекты (в том числе, вложенные), коллекции и всё остальное.
К сожалению, десериализация через source generators пока не поддерживается. Единственное, что разработчики добавили, — это поддержку в стандартном десериализаторе сгенерированных типов:
// Способ 1 JsonSerializer.Deserialize(json, JsonContext.Default.JsonMessage); // Способ 2 JsonSerializer.Deserialize(json, typeof(JsonMessage), JsonContext.Default);
Но даже в этом случае никаких Utf8JsonReader не будет. Только рефлексия, только хардкор.
Поддержка нотификаций при (де)сериализации
В специальный неймспейс System.Text.Json.Serialization добавили четыре интерфейса: IJsonOnDeserialized , IJsonOnDeserializing , IJsonOnSerialized и IJsonOnSerializing . Они нужны для вызова методов в процессе (де)сериализации. Как правило, в целях валидации:
public class Person : IJsonOnDeserialized, IJsonOnSerializing < public string FirstName< get; set; >void IJsonOnDeserialized.OnDeserialized() => Validate(); // Call after deserialization void IJsonOnSerializing.OnSerializing() => Validate(); // Call before serialization private void Validate() < if (FirstName is null) < throw new InvalidOperationException("The 'FirstName' property cannot be 'null'."); >> >
Но вы можете придумать и какое-нибудь своё применение.
Порядок следования полей при сериализации
С помощью специального атрибута JsonPropertyOrder теперь можно управлять порядком, в котором сериализованные поля будут помещаться в итоговый JSON:
public class Person < public string City < get; set; >// No order defined (has the default ordering value of 0) [JsonPropertyOrder(1)] // Serialize after other properties that have default ordering public string FirstName < get; set; >[JsonPropertyOrder(2)] // Serialize after FirstName public string LastName < get; set; >[JsonPropertyOrder(-1)] // Serialize before other properties that have default ordering public int Id < get; set; >>
Ранее порядок, в котором поля попадали в итоговый JSON, был, скажем так, не совсем предсказуем.
Utf8JsonWriter : возможность вывести напрямую JSON-текст
В класс System.Text.Json.Utf8JsonWriter добавили метод WtiteRawValue , и теперь в JSON можно писать raw-текст:
JsonWriterOptions writerOptions = new() < WriteIndented = true, >; using MemoryStream ms = new(); using UtfJsonWriter writer = new(ms, writerOptions); writer.WriteStartObject(); writer.WriteString("dataType", "CalculationResults"); writer.WriteStartArray("data"); foreach (CalculationResult result in results) < writer.WriteStartObject(); writer.WriteString("measurement", result.Measurement); writer.WritePropertyName("value"); // Write raw JSON numeric value using FormatNumberValue (not defined in the example) byte[] formattedValue = FormatNumberValue(result.Value); writer.WriteRawValue(formattedValue, skipValidation: true); writer.WriteEndObject(); >writer.WriteEndArray(); writer.WriteEndObject();
В данном примере решается одна известная проблема сериализатора: он не пишет дробную часть вещественного числа, если она равна нулю. То есть, число 1.1 будет выведено с вещественной частью, а число 1.0 будет выведено как целое. В ряде случаев такое поведение неприемлемо, и теперь вы можете управлять им с помощью нового метода.
На момент написания этих строк метод WriteRawValue готов, но ещё не добавлен в основную ветку кода и болтается в одном из пулл-реквестов. Но сама фича заппрувлена в итоговый релиз. Просто ещё не дошёл ход до неё.
Десериализация из Stream
Оказывается, раньше не было возможности десериализовать поток. Теперь есть:
using MemoryStream ms = GetMyStream(); MyPoco poco = JsonSerializer.Deserialize(ms);
И эта фича тоже пока болтается в пулл-реквесте.
Новая коллекция PriorityQueue
Она представляет собой ту же самую очередь, но каждый элемент которой теперь имеет приоритет. При помещении элемента в очередь вы этот приоритет указываете. При извлечении элемента из очереди сначала извлекаются те элементы, приоритет которых имеет минимальное значение:
// creates a priority queue of strings with integer priorities var pq = new PriorityQueue(); // enqueue elements with associated priorities pq.Enqueue("A", 3); pq.Enqueue("B", 1); pq.Enqueue("C", 2); pq.Enqueue("D", 3); pq.Dequeue(); // returns "B" pq.Dequeue(); // returns "C" pq.Dequeue(); // either "A" or "D", stability is not guaranteed.
Как видно из примера, в случае равенства приоритетов порядок извлечения элементов не гарантирован.
Очень интересная коллекция, вполне подойдёт для некоторых случаев.
Source Generator для ILogger
Новая фича .NET 5 — Source Generators — добралась до логгера. Теперь можно писать меньше кода для логгинга, потому что недостающий код будет создан автоматически. Вам достаточно лишь пометить специальные методы специальным атрибутом LoggerMessageAttribute , и весь недостающий код будет скомпилирован за вас, причём, он будет более оптимальным и производительным.
public static partial class Log < [LoggerMessage(EventId = 0, Level = LogLevel.Critical, Message = "Could not open socket to ``")] public static partial void CouldNotOpenSocket(ILogger logger, string hostName); >
Только не забудьте пометить и метод, и содержащий его класс ключевым словом partial .
Детали уже можно почитать в документации.
Улучшения в System.Linq
В LinqExtensions добавили массу полезных методов и фич. Например, поддержку диапазонов и индексов. Теперь можно попросить вернуть второй с конца элемент коллекции:
Enumerable.Range(1, 10).ElementAt(^2); // returns 9
А в метод Take() добавили классную перегрузку:
source.Take(..3); // instead of source.Take(3) source.Take(3..); // instead of source.Skip(3) source.Take(2..7); // instead of source.Take(7).Skip(2) source.Take(^3..); // instead of source.TakeLast(3) source.Take(..^3); // instead of source.SkipLast(3) source.Take(^7..^3); // instead of source.TakeLast(7).SkipLast(3)
Новый метод TryGetNonEnumeratedCount() сильно помогает в случаях, когда надо узнать количество элементов коллекции без её перебора:
List buffer = source.TryGetNonEnumeratedCount(out int count) ? new List(capacity: count) : new List(); foreach (T item in source)
Если source — это просто переменная типа IEnumerable , то попытка получить количество элементов коллекции может вызывать полный перебор коллекции раньше времени. А с помощью TryGetNonEnumeratedCount() можно и рыбку съесть узнать количество элементов для аллокации соответствующего массива, и полный перебор отложить на более подходящее время.
Четыре новых метода DistinctBy / UnionBy / IntersectBy / ExceptBy теперь позволяют явно указывать поле-селектор:
Enumerable.Range(1, 20).DistinctBy(x => x % 3); // var first = new (string Name, int Age)[] < ("Francis", 20), ("Lindsey", 30), ("Ashley", 40) >; var second = new (string Name, int Age)[] < ("Claire", 30), ("Pat", 30), ("Drew", 33) >; first.UnionBy(second, person => person.Age); //
А в дополнение к ним завезли ещё два аналогичных метода: MaxBy / MinBy .
var people = new (string Name, int Age)[] < ("Francis", 20), ("Lindsey", 30), ("Ashley", 40) >; people.MaxBy(person => person.Age); // ("Ashley", 40)
А этого иногда сильно не хватало.
Странно, что до этого не додумались раньше, но теперь это есть. Методы FirstOrDefault / LastOrDefault / SingleOrDefault позволяют указывать дефолтное значение, как это делается в методе nullable-типов GetValueOrDefault :
Enumerable.Empty().SingleOrDefault(-1); // returns -1
Ну и напоследок. Метод Zip теперь имеет перегрузку для итерации по трём коллекциям:
var xs = Enumerable.Range(1, 10); var ys = xs.Select(x => x.ToString()); var zs = xs.Select(x => x % 2 == 0); foreach ((int x, string y, bool z) in Enumerable.Zip(xs,ys,zs))
Кто-то вообще в курсе, что так можно было?
Дата и время
Борьба со временем — это не только метафора из жизни, это ещё и вполне себе реальное явление в мире разработки ПО. Довольно большая часть разработчиков просто не умеет правильно работать с датой и временем. Дополнительного масла в огонь невежества подливают фреймворки, предоставляя вместо правильных инструментов универсальные.
Разработчики .NET решили немного то ли облегчить, то ли усугубить страдания и добавили пару новых структур: DateOnly и TimeOnly , а также немного подшаманили с поддержкой временных зон и ещё по мелочи. Достаточно подробный обзор этих нововведений уже есть в этой переводной статье. И он обязателен к прочтению и глубокому осмыслению.
Preview Features и сразу Generic Math
Выпуск новых версий .NET уже давно встал на поток: в год — по LTS-версии. Это значительно быстрее, чем было раньше с классическим фреймворком, и это хорошо с одной стороны: можно оперативнее реагировать на запросы пользователей, быстрее выкатывать полезные фичи и вообще — не тормозить.
Но внезапно это породило и проблемы с некоторым особенно сложным в разработке функционалом. Разработчики .NET столкнулись с тем, что некоторые полезные новые фичи они физически не успевают выпустить, протестировать и внедрить во всех библиотеках за год. Не говоря уже о том, чтобы собрать фидбек по ним.
И они придумали механизм Preview Features. Теперь LTS-версию .NET можно будет поставлять с недоделанными превью-фичами. То, что раньше было доступно только в превью- и RC-версиях фреймворка, отныне может совершенно легально попасть в библиотеки, компиляторы и продакшн. В целях безопасности это всё обвешано атрибутами и настройками, чтобы по умолчанию быть выключенным. То есть, вы не сможете это использовать, специально не заморочившись. А вот захотите вы заморачиваться или нет — дело ваше.
Возможно, вам понравится первая превью-фича, для которой разработали весь этот механизм: статические абстрактные методы интерфейсов. Эта фича как раз из тех, что довольно сложно внедрить быстро. Её не успели обкатать в превью-версиях .NET 6 и решили выпустить в LTS-версии в том виде, в каком успеют реализовать к релизу. Поскольку это превью-фича, то нет никаких гарантий, что она не изменится даже в ближайших двух RC-выпусках .NET 6. Более того: нет никаких гарантий, что она не изменится в апдейтах .NET 6 после релиза. На этой новой фиче построен механизм арифметики в обобщениях. Детально об этом можно почитать в статье, и звучит это неплохо.
Итого, с помощью нового механизма превью-фич и разработчики .NET, и разработчики на .NET получают лишнее время на обкатку интересных идей. Вопрос: а не выльется ли это в конце концов в то же самое, во что превратились HTML и CSS? Ну, то есть, когда нумерация версий уже не имеет значения, а регулярно добавляемые фичи сначала какое-то время находятся в превью-стадии, а затем, после тестирования и доработок, просто переходят в спецификацию?
Больше анализаторов богу анализа!
С компилятором Roslyn наступила эра Roslyn-анализаторов, которые, вообще-то, здорово помогают в разработке. В .NET 5 в компилятор уже было встроено порядка 250 различных анализаторов, и ещё больше можно было скачать в виде nuget-пакетов. С какого-то момента команда dotnet build выводит дикое количество уведомлений от анализаторов о том, что разработчик говнокодит пишет что-то не то. С одной стороны, эти предупреждения, генерируемые анализаторами, помогают заметить косяки и сделать код чище. С другой стороны, всё равно есть много ложных срабатываний, в которых теряются действительно важные замечания. Это всё можно настроить, но на это нужно время.
В .NET 6 решили не останавливаться на достигнутом, и теперь встроенных анализаторов ещё больше (правда, среди них есть и те, что ранее поставлялись отдельно). Окинув беглым взглядом список новых анализаторов, нельзя не признать, что среди них, безусловно, много полезных.
В общем, читайте портянки компилятора во время вывода и настраивайте свою среду разработки так, чтобы не игнорировать подсказки анализаторов.
API для выделения памяти
Лёгким движением руки C# можно превратить в C. И это почти не шутка: в .NET 6 завезли нативное выделение памяти. За это дело отвечают специальные методы в новом классе System.Runtime.InteropServices.NativeMemory .
namespace System.Runtime.InteropServices < public static class NativeMemory < public static unsafe void* Alloc(nuint byteCount); public static unsafe void* Alloc(nuint elementCount, nuint elementSize); public static unsafe void* AllocZeroed(nuint byteCount); public static unsafe void* AllocZeroed(nuint elementCount, nuint elementSize); public static unsafe void* Realloc(void* ptr, nuint byteCount); public static unsafe void Free(void* ptr); public static unsafe void* AlignedAlloc(nuint byteCount, nuint alignment); public static unsafe void AlignedFree(void* ptr); public static unsafe void* AlignedRealloc(void* ptr, nuint byteCount, nuint alignment); >>
К чёрту управляемые ресурсы, к чёрту сборщик мусора. Да здравствуют alloc и free методы! Разумеется, всячески подчёркивается, что это для низкоуровневого кода и алгоритмов. Но мы как-то упустили упустили момент, когда C# стал позиционироваться как язык для таких вещей.
Разработчики очень серьёзно заигрались в оптимизацию. Пока нет единого мнения насчёт того, хорошо это или плохо. Фанаты оптимизаций приветствуют такие возможности. Но людей с определённым профессиональным опытом настораживает попытка создать универсальную платформу для всего сразу.
Что будет дальше? Добавление возможности писать прямые ассемблерные инструкции в коде? Или движение в сторону C++? Подождём .NET 7.
И так далее
Список нововведений в .NET куда больше, чем описано в этой статье. Но надо когда-нибудь остановиться, потому что описать все детали в одном посте — это перебор. Просто быстренько пройдёмся по оставшемуся:
- пул потоков полностью переписан с нативного на управляемый код;
- оптимизация работы со структурами: они теперь могут целиком передаваться как параметры через регистры процессора;
- ускорено приведение и проверка интерфейсных типов (будет быстрее работать Pattern Matching);
- с помощью прекрасной новой команды dotnet sdk check вы можете проверить актуальность ваших SDK;
- вебсокеты поддерживают компрессию;
- BigInteger теперь парсит строки почти на 90% быстрее;
- Vector теперь поддерживает примитивы nint и nuint ;
- добавлена поддержка OpenTelemetry.
Но даже это не окончательный список.
А самое главное — за кадром остались нововведения в языке C# 10. Об этом — в другой раз.