Перейти к содержимому

Asp net core 6 что нового

  • автор:

Asp net core 6 что нового

8 ноября компания Microsoft выпустила финальную версию фреймворка .NET 6.0 с C# 10 и F# 6, а также Visual Studio 2022. .NET 6 ознаменовался значительным увеличением поизводительности по сравнению с предыдущими версиями фреймворка и большим количеством нового функционала. Кроме того, это LTS-версия, которая будет поддерживаться в течении следующих трех лет.

.NET 6.0 представляет унифицированную платформу, которая позволяет создавать десктопные и мобильные и веб-приложения, а также приложения для IoT и облака.

.NET 6 и C# 10

Некоторые основные изменения в этой версии:

  • Благодаря внедрению Dynamic Profile-guided Optimization (PGO) удалось повысить производительность. Dynamic PGO базируется на механизме уровневой компиляции Tiered Compilation, которая позволяет произвести на старте быструю базовую компиляцию методов («уровень 0»), а когда метод потребует, произвести его перекомпиляцию с рядом оптимизаций («уровень 1»)
  • Полностью переписан API для работы с файлами, в частности, класс FileStream . Это привело к значительному увеличению производительности: увеличилась скорость выполнения и значительно уменьшилось выделение памяти.
  • Более быстрая проверка интерфейсов и использование их в преобразованиях типов. По данным Microsoft, производительность увеличилась на 16% – 38%.
  • Добавлены генераторы кода для API System.Text.Json , что опять же повысило производительность и уменьшило выделение памяти. Кроме того, JsonSerializer поддерживает 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); //

    Глобальные пространства имен. Достаточно подключить пространство имен в один файл кода с помощью директивы global using , и оно будет доступно во всех файлах проекта.
global using System; global using static System.Console; global using Env = System.Environment;
namespace MyNamespace < class MyClass < . >>

Можно писать

namespace MyNamespace; class MyClass < . >// Not indented
public record struct Person < public string FirstName < get; init; >public string LastName < get; init; >>
var handler = new HttpClientHandler < Proxy = new WebProxy("socks5://127.0.0.1", 9050) >; var httpClient = new HttpClient(handler);
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(); >
Enumerable.Range(1, 10).ElementAt(^2); // 9 source.Take(..3) вместо source.Take(3) source.Take(3..) вместо source.Skip(3) source.Take(2..7) вместо source.Take(7).Skip(2) source.Take(^3..) вместо source.TakeLast(3) source.Take(..^3) вместо source.SkipLast(3) source.Take(^7..^3) вместо source.TakeLast(7).SkipLast(3).

Также добавлены новые методы DistinctBy/UnionBy/IntersectBy/ExceptBy/MaxBy/MinBy А в методах FirstOrDefault/LastOrDefault/SingleOrDefault теперь можно установить значение по умолчанию:

Enumerable.Empty().SingleOrDefault(-1); // -1
// очередь из строк, числа устанавливают приоритеты var pq = new PriorityQueue(); // получение элементов очереди pq.Enqueue("A", 3); pq.Enqueue("B", 1); pq.Enqueue("C", 2); pq.Enqueue("D", 3); pq.Dequeue(); // "B" pq.Dequeue(); // "C" pq.Dequeue(); // либо "A", либо "D"

Отмечается, что .NET 6 поддерживается в Visual Studio 2022 и Visual Studio 2022 for Mac. Однако НЕ поддерживается в Visual Studio 2019 и Visual Studio for Mac 8. Кроме того, .NET 6 поддерживается в Visual Studio Code с помощью расширения для C#.

Обновления 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 доступны заключительные части плана унификации .NET, который был представлен в .NET 5. .NET 6 объединяет пакет SDK, базовые библиотеки и среду выполнения для мобильных, классических, облачных приложений и приложений Интернета вещей. Помимо этого экосистема .NET 6 предлагает следующие возможности:

  • Упрощенная разработка: приступить к работе очень просто. Новые возможности языка в C# 10 позволяют сократить объем создаваемого кода. А инвестиции в веб-решения и минимальные API позволяют быстро создавать небольшие и быстродействующие микрослужбы.
  • Более высокая производительность: .NET 6 — это самая быстрая веб-платформа комплексной разработки, которая снижает затраты на вычислительные ресурсы при работе в облаке.
  • Максимальная продуктивность: NET 6 и Visual Studio 2022 предоставляют возможности горячей перезагрузки, предлагают новые средства Git, инструменты интеллектуального редактирования кода, надежные средства диагностики, тестирования и более эффективной совместной работы групп.

.NET 6 будет поддерживаться в течение трех лет в качестве выпуска с долгосрочной поддержкой (LTS).

Предварительные версии функций по умолчанию отключены. Они также не поддерживаются для использования в рабочей среде и могут быть удалены в следующей версии. Новый RequiresPreviewFeaturesAttribute используется для добавления заметок к предварительным версиям API, а соответствующий анализатор выводит предупреждения, если вы используете эти API.

.NET 6 поддерживается средами Visual Studio 2022 и Visual Studio 2022 для Mac (и более поздних версий).

В этой статье рассматривается лишь часть новых возможностей .NET 6. Чтобы узнать обо всех новых функциях, а также получить дополнительные сведения о возможностях, приведенных в этой статье, ознакомьтесь с записью блога о выходе .NET 6.

Производительность

FileStream

Тип System.IO.FileStream был переработан для .NET 6, чтобы обеспечивать лучшую производительность и надежность в ОС Windows. Теперь FileStream никогда не блокируется при создании асинхронного ввода-вывода в Windows. Дополнительные сведения см. в записи блога об улучшениях файлового ввода-вывода в .NET 6.

Профильная оптимизация

Смысл профильной оптимизаций (PGO) заключается в том, что JIT-компилятор создает оптимизированный код с учетом самых часто используемых типов и путей кода. В .NET 6 появилась динамическая профильная оптимизация. Она работает в тесной связке с многоуровневой компиляцией для дальнейшей оптимизации кода на основе дополнительного инструментария, размещаемого на уровне 0. Динамический PGO отключен по умолчанию, но его можно включить с переменной DOTNET_TieredPGO среды. Дополнительные сведения см. в статье об улучшениях производительности JIT-компиляции.

Crossgen2

В .NET 6 представлено средство Crossgen2, последователь удаленного Crossgen. Crossgen и Crossgen2 обеспечивают предварительную компиляцию (AOT) для сокращения времени запуска приложения. Средство Crossgen2 написано на C#, а не C++, и может выполнять операции анализа и оптимизации, которые были недоступны в предыдущей версии. Дополнительные сведения см. в статье о Crossgen2.

Поддержка Arm64

В выпуске .NET 6 представлена поддержка macOS Arm64 (или «Apple Silicon») и Windows Arm64 как для работы в собственном режиме Arm64 , так и в режиме эмуляции x64. Кроме того, установщики .NET x64 и Arm64 теперь устанавливаются параллельно. Дополнительные сведения см. в статье о поддержке .NET для macOS 11 и Windows 11 для Arm64 и x64.

Горячая перезагрузка

Горячая перезагрузка — это функция, которая позволяет изменять исходный код приложения и мгновенно применять внесенные изменения к работающему приложению. Ее цель — повысить продуктивность разработчиков, исключив перезапуски приложения между редактированиями. Горячая перезагрузка доступна в Visual Studio 2022 и программе командной строки dotnet watch . Горячая перезагрузка работает с большинством типов приложений NET, а также с исходным кодом на C#, Visual Basic и C++. Дополнительные сведения см. в записи блога о горячей перезагрузке.

.NET MAUI

.NET Multi-Platform App UI (.NET MAUI) все еще находится на этапе предварительной версии, релиз-кандидат выходит в первом квартале 2022 г., а общедоступная версия — во втором квартале 2022 г. .NET MAUI позволяет создавать собственные клиентские приложения для классических и мобильных операционных систем с одной базой кода. Дополнительные сведения см. в записи блога об обновлении .NET Multi-Platform App UI.

C# 10 и шаблоны

В C# 10 включены такие нововведения, как global using директивы, объявления пространств имен с областью действия файла и структуры записей. Дополнительные сведения см. в статье Новые возможности в C# 10.

В соответствии с этой работой, шаблоны проектов в пакете SDK для .NET для C# были модернизированы для использования некоторых новых возможностей языка.

  • async Main Метод
  • Инструкции верхнего уровня
  • Новые выражения целевого типа
  • Неявные директивы global using
  • Пространства имен с областью действия файла
  • Ссылочные типы, допускающие значение null

Если эти новые возможности языка добавляются в шаблоны проектов, новый код запускается с включенными возможностями. Однако при обновлении до .NET 6 существующий код не затрагивается. Дополнительные сведения об этих изменениях шаблонов см. в записи блога о модернизации шаблонов проектов C# в пакете SDK для .NET.

F# и Visual Basic

В F# 6 добавлено несколько улучшений языка F # и F# Interactive. Дополнительные сведения см. в статье Новые возможности в F# 6.

В Visual Basic улучшены возможности работы с Visual Studio и запуска проекта Windows Forms.

Рабочие нагрузки для пакета SDK

Чтобы уменьшить размер пакета SDK для .NET, некоторые компоненты были помещены в новые, необязательные рабочие нагрузки для пакета SDK. К этим компонентам относятся .NET MAUI и Blazor WebAssembly AO. Если вы используете Visual Studio, среда установит все необходимые рабочие нагрузки для пакета SDK. При использовании .NET CLIможно управлять рабочими нагрузками с помощью новых команд dotnet workload .

Команда Description
dotnet workload search Выполняет поиск доступных рабочих нагрузок.
dotnet workload install Устанавливает указанную рабочую нагрузку.
dotnet workload uninstall Удаляет указанную рабочую нагрузку.
dotnet workload update Обновляет установленные рабочие нагрузки.
dotnet workload repair Переустанавливает все установленные рабочие нагрузки для восстановления поврежденной установки.
dotnet workload list Выводит список установленных рабочих нагрузок.

API-интерфейсы System.Text.Json

В System.Text.Json в .NET 6 внесено множество улучшений, так что теперь это решение сериализации промышленного класса.

Генератор исходного кода

В .NET 6 добавлен новый генератор исходного кода для System.Text.Json. Генератор исходного кода работает с JsonSerializer и может быть настроено несколькими способами. Он может повысить производительность, сократить использование памяти и упростить усечение сборок. Дополнительные сведения см. в статьях о выборе отражения или создания исходного кода в System.Text.Json и о создании исходного кода в System.Text.Json.

Записываемая модель DOM

Добавлена новая записываемая модель DOM, которая дополняет уже существующую модель DOM только для чтения. Новый API предоставляет упрощенную альтернативу сериализации для случаев, когда использовать простые старые типов POCO невозможно. Он также позволяет эффективно переходить к подразделу большого дерева JSON и считывать из него массив или десериализовывать POCO. Для поддержки записываемой модели DOM были добавлены следующие новые типы:

Дополнительные сведения см. в статье о видах DOM JSON.

Сериализация IAsyncEnumerable

Другие новые API-интерфейсы

Новые интерфейсы сериализации для проверки и значения по умолчанию:

  • IJsonOnDeserialized
  • IJsonOnDeserializing
  • IJsonOnSerialized
  • IJsonOnSerializing

Дополнительные сведения см. в статье Обратные вызовы.

Новый атрибут сортировки свойств:

  • JsonPropertyOrderAttribute Дополнительные сведения см. в статье Настройка порядка сериализованных свойств.

Новый метод для написания «необработанного» JSON:

  • Utf8JsonWriter.WriteRawValue Дополнительные сведения см. в статье Написание необработанного JSON

Синхронная сериализация и десериализация в поток:

  • JsonSerializer.Deserialize(Stream, Type, JsonSerializerOptions)
  • JsonSerializer.Deserialize(Stream, Type, JsonSerializerContext)
  • JsonSerializer.Deserialize(Stream, JsonSerializerOptions)
  • JsonSerializer.Deserialize(Stream, JsonTypeInfo)
  • JsonSerializer.Serialize(Stream, Object, Type, JsonSerializerOptions)
  • JsonSerializer.Serialize(Stream, Object, Type, JsonSerializerContext)
  • JsonSerializer.Serialize(Stream, TValue, JsonSerializerOptions)
  • JsonSerializer.Serialize(Stream, TValue, JsonTypeInfo)

Новый параметр для пропуска объекта при обнаружении цикла ссылок во время сериализации:

  • ReferenceHandler.IgnoreCycles Дополнительные сведения см. в статье Пропуск циклических ссылок.

Дополнительные сведения о сериализации и десериализации с помощью System.Text.Json см. в статье Сериализация и десериализация JSON в .NET.

HTTP/3

В .NET 6 включена поддержка предварительной версии HTTP/3 — новой версии HTTP. HTTP/3 решает некоторые существующие проблемы с функциональностью и производительностью, используя новый базовый протокол подключения QUIC. QUIC быстрее устанавливает подключения быстрее, а подключения не зависят от IP-адреса, что позволяет мобильным клиентам перемещаться между сетями Wi-Fi и сетями сотовой связи. Дополнительные сведения см. в статье Использование HTTP/3 с HttpClient.

ASP.NET Core

ASP.NET Core содержит усовершенствования для минимальных API-интерфейсов, предварительной компиляции (AOT) для приложений Blazor WebAssembly и одностраничных приложений. Кроме того, компоненты Blazor теперь можно отрисовыть из JavaScript и интегрировать с существующими приложениями на основе JavaScript. Дополнительные сведения см. в статье Новые возможности в ASP.NET Core 6.

OpenTelemetry

.NET 6 предоставляет улучшенную поддержку OpenTelemetry, которая представляет собой набор средств, API и пакетов SDK, помогающих анализировать производительность и поведение программного обеспечения. API-интерфейсы в пространстве имен System.Diagnostics.Metrics реализуют спецификацию API метрик OpenTelemetry. Например, существует четыре класса инструментирования для поддержки различных сценариев использования метрик. Классы инструментирования:

Безопасность

В .NET 6 добавлена предварительная версия поддержки двух ключевых мер защиты: Control-flow Enforcement Technology (CET) и политика защиты памяти «write exclusive execute» (W^X).

CET — это технология Intel, доступная в некоторых современных процессорах Intel и AMD. Она добавляет в оборудование возможности, которые защищают от некоторых атак с перехватом потока управления. .NET 6 обеспечивает поддержку CET для приложений Windows x64, которую необходимо включить явным образом. Дополнительные сведения см. в статье Совместимость .NET 6 с теневыми стеками CET Intel.

Защита W^X доступна для всех операционных систем с .NET 6, но по умолчанию включена только в Apple Silicon. W^X блокирует простейшее направление атаки, запрещая одновременную запись и выполнение страниц памяти.

Обрезка IL

Улучшена обрезка автономных развертываний. В .NET 5 обрезались только неиспользуемые сборки. В .NET 6 также добавлена обрезка неиспользуемых типов и членов. Кроме того, предупреждения об обрезке, сообщающие о местах, где функция обрезки может удалить код, используемый во время выполнения, теперь включены по умолчанию. Дополнительные сведения см. в статье Обрезка автономных развертываний и исполняемых файлов.

Анализ кода

Пакет SDK для .NET 6 включает несколько новых анализаторов кода, имеющих отношение к совместимости API, совместимости платформы, безопасности обрезки, использования диапазона при сцеплении и разделении строк, ускоренных строковых API и ускоренных API сбора. Полный список новых (и удаленных) анализаторов см. в статье Выпуски анализаторов — .NET 6.

Пользовательские средства защиты платформы

Анализатор совместимости платформ распознает методы Is в классе OperatingSystem, например OperatingSystem.IsWindows(), как средства защиты платформы. Для использования пользовательских средств защиты платформы в .NET 6 появились два новых атрибута, которые можно использовать для добавления заметок к полям, свойствам или методам с поддерживаемым или неподдерживаемым именем платформы:

  • SupportedOSPlatformGuardAttribute
  • UnsupportedOSPlatformGuardAttribute

Windows Forms

Application.SetDefaultFont(Font) — это новый метод в .NET 6, который задает шрифт по умолчанию для приложения.

Шаблоны для приложений Windows Forms на C# были обновлены для поддержки директив, пространств имен с областью действия файла и ссылочных типов, допускающих значение NULL, в global using . Кроме того, они включают в себя код начальной загрузки приложения, который сокращает стандартный код и позволяет конструктору Windows Forms отображать область конструктора в предпочтительном шрифте. Код начальной загрузки — это вызов ApplicationConfiguration.Initialize() , который является исходным созданным методом, осуществляющим вызовы к другим методам конфигурации, таким как Application.EnableVisualStyles(). Кроме того, если задать шрифт, отличный от используемого по умолчанию, с помощью свойства MSBuild ApplicationDefaultFont, ApplicationConfiguration.Initialize() осуществляет вызов SetDefaultFont(Font).

Дополнительные сведения см. в записи блога Новые возможности в Windows Forms.

Исходный код

Архив Tarball исходного кода, содержащий весь исходный код для пакета SDK для .NET, теперь является продуктом сборки пакета SDK для .NET. Используя этот архив Tarball исходного кода, другие организации, например Red Hat, могут создать собственную версию пакета SDK.

Моникеры целевой платформы

Были добавлены дополнительные моникеры целевой платформы, соответствующие конкретной операционной системе (TFM), например net6.0-android , net6.0-ios и net6.0-macos . Дополнительные сведения см. в статье TFM для конкретной ОС в .NET 5 и более поздних версиях.

Арифметические операторы в универсальных типах

В предварительной версии .NET 6 можно использовать операторы в универсальных типах. В .NET 6 представлено множество интерфейсов, в которых используется новая предварительная версия функции C# 10 — члены интерфейса static abstract . Эти интерфейсы соответствуют разным операторам. Например, IAdditionOperators представляет оператор + . Интерфейсы доступны в пакете NuGet System.Runtime.Experimental. Дополнительные сведения см. в записи блога о Generic math.

Проверка пакета NuGet

Если вы занимаетесь разработкой библиотеки NuGet, новое средство проверки пакетов позволяет проверять целостность пакетов и правильность их форматов. Можно определить следующие моменты:

  • наличие критических изменений в версиях пакета;
  • Пакет имеет один и тот же набор общедоступных API для всех реализаций, относящихся к среде выполнения.
  • наличие проблем в отношении применимости целевой платформы или среды выполнения.

Дополнительные сведения см. в записи блога Проверка пакета.

API-интерфейсы отражения

В .NET 6 появились следующие новые API-интерфейсы, которые проверяют код и предоставляют сведения о допустимости значений NULL:

  • System.Reflection.NullabilityInfo
  • System.Reflection.NullabilityInfoContext
  • System.Reflection.NullabilityState

Эти API-интерфейсы полезны при использовании для средств на основе отражения и сериализаторов.

API-интерфейсы Microsoft.Extensions

В .NET 6 внесены улучшения в ряд пространств имен расширений, как показано в таблице ниже.

Пространство имен Усовершенствования
Microsoft.Extensions.DependencyInjection CreateAsyncScope позволяет безопасно использовать оператор using для поставщика услуг, который регистрирует службу IAsyncDisposable.
Microsoft.Extensions.Hosting Новые методы ConfigureHostOptions упрощают настройку приложения.
Microsoft.Extensions.Logging В Microsoft.Extensions.Logging реализован новый генератор исходного кода для высокопроизводительных API ведения журналов. Генератор исходного кода активируется при добавлении нового LoggerMessageAttribute в метод ведения журналов partial . Во время компиляции генератор создает реализацию метода partial , который, как правило, действует быстрее во время выполнения, чем существующие решения ведения журналов. Дополнительные сведения см. в статье Создание исходного кода ведения журналов во время компиляции.

Новые API-интерфейсы LINQ

В .NET 6 было добавлено множество методов LINQ. Большинство новых методов, приведенных в таблице ниже, имеют эквивалентные методы в типе System.Linq.Queryable.

Метод Description
Enumerable.TryGetNonEnumeratedCount(IEnumerable, Int32) Пытается определить количество элементов в последовательности без принудительного перечисления.
Enumerable.Chunk(IEnumerable, Int32) Разделяет элементы последовательности на фрагменты указанного размера.
Enumerable.MaxBy и Enumerable.MinBy Находит максимальные или минимальные элементы с помощью селектора ключа.
Enumerable.DistinctBy, Enumerable.ExceptBy, Enumerable.IntersectBy и Enumerable.UnionBy Эти новые разновидности методов, выполняющих операции на основе наборов, позволяют указывать равенство с помощью функции селектора ключа.
Enumerable.ElementAt(IEnumerable, Index) и Enumerable.ElementAtOrDefault(IEnumerable, Index) Принимает индексы, отсчитываемые от начала или конца последовательности, например Enumerable.Range(1, 10).ElementAt(^2) возвращается 9 .
Enumerable.FirstOrDefault(IEnumerable, TSource) и Enumerable.FirstOrDefault(IEnumerable, Func, TSource)
Enumerable.LastOrDefault(IEnumerable, TSource) и Enumerable.LastOrDefault(IEnumerable, Func, TSource)
Enumerable.SingleOrDefault(IEnumerable, TSource) и Enumerable.SingleOrDefault(IEnumerable, Func, TSource)
Новые перегрузки позволяют указывать значение по умолчанию, которое будет использоваться, если последовательность пуста.
Enumerable.Max(IEnumerable, IComparer) и Enumerable.Min(IEnumerable, IComparer) Новые перегрузки позволяют указывать функцию сравнения.
Enumerable.Take(IEnumerable, Range) Принимает Range аргумент для упрощения принятия среза последовательности, например, можно использовать source.Take(2..7) вместо source.Take(7).Skip(2) него.
Enumerable.Zip(IEnumerable, IEnumerable, IEnumerable) Создает последовательность кортежей с элементами из трех указанных последовательностей.

Улучшения даты, времени и часового пояса

В .NET 6 были добавлены следующие две структуры: System.DateOnly и System.TimeOnly. Они представляют часть даты и часть времени DateTime, соответственно. DateOnly можно использовать для дней рождения и годовщин, а TimeOnly подходит для ежедневных оповещений и еженедельных рабочих часов.

Теперь в любой операционной системе с установленными данными о часовом поясе можно использовать функцию управления адресными пространствами Интернета (IANA) или идентификаторы часовых поясов Windows. Метод TimeZoneInfo.FindSystemTimeZoneById(String) был обновлен для автоматического преобразования входных данных из часового пояса Windows в часовой пояс IANA (или наоборот), если запрошенный часовой пояс не найден в системе. Кроме того, были добавлены новые методы TryConvertIanaIdToWindowsId(String, String) и TryConvertWindowsIdToIanaId для сценариев, когда по-прежнему необходимо вручную выполнять преобразование из одного формата часового пояса в другой.

Также доступны и другие улучшения часовых поясов. Дополнительные сведения см. в статье Улучшения даты, времени и часовых поясов в .NET 6.

Класс PriorityQueue

См. также

  • Новые возможности C# 10
  • Новые возможности F# 6
  • Новые возможности EF Core 6
  • Новые возможности в ASP.NET Core 6
  • Заметки о выпуске .NET 6
  • Заметки о выпуске Visual Studio 2022
  • Запись блога: объявление о выпуске .NET Core 6
  • Запись блога: новый генератор исходного кода System.Text.Json

Что нового в .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. Об этом — в другой раз.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *