>
>
>
Что нового в .NET 7?

Артём Ровенский
Статей: 23

Что нового в .NET 7?

Вышел .NET 7, а это значит, что можно вдоволь насладиться различными нововведениями и фишками. Расскажем про самые интересные улучшения: C# 11, контейнеры, производительность, GC и прочее.

C# 11

Мы уже выпустили статью, посвящённую разбору нововведений в C# 11. В ней мы прошлись по новым особенностям: обобщённой математике, исходным строкам, модификатору required, параметрам типа в атрибутах и прочему.

Кстати, мы уже работаем над поддержкой .NET 7 и C# 11 — она будет добавлена в PVS-Studio 7.22. Релиз запланирован на начало декабря, а загрузить свежую версию анализатора можно будет здесь. Если хотите попробовать бету уже сейчас, напишите нам :).

Native AOT

AOT (ahead-of-time) – компиляция приложения не в промежуточный, а сразу в машинный код. Native AOT использует ahead of time компилятор для компиляции IL в машинный код во время публикации self-contained приложения. Native AOT был переведён из статуса экспериментального. Основными преимуществами native AOT приложений являются:

  • время запуска;
  • использование памяти;
  • работа на платформах с ограничениями (JIT недоступен).

Native AOT приложения имеют ряд ограничений:

  • нет динамической загрузки (например, Assembly.LoadFile);
  • нет генерации кода во время выполнения (например, System.Reflection.Emit);
  • нет C++/CLI;
  • и т. д.

On Stack Replacement (OSR)

В .NET существует такое понятие, как многоуровневая компиляция (Tiered Compilation). Если говорить простым языком, то многоуровневая компиляция снижает время запуска приложения. Как? Изначально JIT будет генерировать плохо оптимизированный машинный код (tier-0) для методов, т. к. это просто требует меньше времени. Если же количество вызовов метода переступает за определённый порог, то JIT cгенерирует для этого метода более оптимизированный код (tier-1). Данный подход не работает, например, с циклами, т. к. это может приводить к ухудшению производительности. К слову, на данный момент существует всего два уровня.

OSR позволяет заменять машинный код, который выполняется в данный момент, на новый — более оптимизированный. Ранее такая возможность была только между вызовами метода. Подобный подход позволяет применять многоуровневую компиляцию ко всем методам. Благодаря этому можно добиться более быстрой компиляции и при этом стабильной производительности. Судя по тестам Microsoft, внедрение данной технологии помогло на 25 % ускорить запуск нагруженных приложений. OSR в .NET 7 включена по умолчанию для x64 и Arm64.

Централизованное управление пакетами (CPM)

Управление зависимостями для многопроектных решений может оказаться сложной задачкой. Теперь же в ситуациях, когда требуется управление общими зависимостями для нескольких проектов, вы можете использовать централизованное управление NuGet пакетами. Для централизованного управления зависимостями потребуется добавить файл Directory.Packages.props в корень решения. Благодаря CPM версия пакета указывается только в Directory.Packages.props, а в проектах требуется лишь сослаться на пакет.

GC Regions

Регионы GC – это функция, которая разрабатывается уже несколько лет. Если раньше приходилось иметь несколько больших сегментов памяти (например, 1 ГБ), то теперь GC поддерживает множество маленьких областей (например, 4 МБ). Это позволяет GC быть более гибким в вопросах перепрофилирования областей памяти из одного поколения в другое.

В .NET 7 регионы используются по умолчанию для 64-битных приложений. Подробности по данному вопросу можно получить из статьи архитектора .NET GC.

Rate Limiting

Rate limiting – это механизм ограничения объёма доступа к ресурсу. Таким образом можно задать определённый лимит доступа, например, к базе данных.

Для написания ограничителя в .NET 7 был добавлен NuGet пакет System.Threading.RateLimiting. В основном работа будет происходить с абстрактным классом RateLimiter. Один из примеров работы от Microsoft:

RateLimiter limiter = GetLimiter();
using RateLimitLease lease = limiter.Acquire(permitCount: 1);
if (lease.IsAcquired)
{
  // Do action that is protected by limiter
}
else
{
  // Error handling or add retry logic
}

В данном случае мы пытаемся получить 1 разрешение с помощью метода Acquire. Далее идёт проверка – было ли получено разрешение:

  • если разрешение было получено, то можем использовать ресурс;
  • если разрешение не было получено, то это можно залогировать или обработать как ошибку.

Встроенная поддержка контейнеров

Теперь можно быстро и легко создавать контейнерные версии своих приложений, используя команду dotnet publish.

Например:

dotnet add package Microsoft.NET.Build.Containers
dotnet publish --os linux --arch x64 -c Release 
-p:PublishProfile=DefaultContainer

В данном случае мы добавляем временную ссылку на пакет для создания контейнера и публикуем проект для linux x64. Результатом выполнения команд является образ, который будет добавлен в Docker. После этого вы можете запустить приложение, используя контейнер:

docker run -it --rm -p 5010:80 my-awesome-container-app:1.0.0

Подробнее про это можно почитать здесь.

Улучшение производительности

Из года в год в .NET растёт производительность. Этот релиз не стал исключением. Только на перечисление всех улучшений нужна отдельная статья, поэтому расскажу только о самых интересных.

Если интересны подробности улучшения производительности в .NET 7, можете почитать о них здесь.

Рефлексия

Существенно сокращены накладные расходы на использование рефлексии, когда вызов выполняется несколько раз для одного и того же элемента (будь то метод, конструктор или свойство). Производительность увеличена в 3-4 раза.

Подробнее про улучшение рефлексии можно прочитать здесь.

LINQ

В .NET 7 повышена производительность LINQ. Например, была существенно улучшена эффективность методов Min и Max при работе с массивами типа int и long. Это достигается за счёт векторизации обработки, а именно – использования Vector<T>. В итоге получаются следующие результаты:

Подробнее про улучшения производительности LINQ можно прочитать в нашей статье.

Кстати, ещё очень здорово, что в System.Linq добавили новые методы Order и OrderDescending. Ранее при использовании OrderBy/OrderByDescending было необходимо ссылаться на собственное значение:

var data = new[] { 2, 1, 3 };
var sorted = data.OrderBy(x => x);
var sortedDesc = data.OrderByDescending(x => x);

Теперь же это не нужно:

var data = new[] { 2, 1, 3 };
var sorted = data.Order();
var sortedDesc = data.OrderDescending();

Регулярные выражения

Сначала я хотел кратко рассказать про улучшения в регулярных выражениях, но в ходе работы над статьёй пришло осознание того, что улучшений слишком много. Поэтому просто оставлю ссылку на большую статью, посвящённую этой теме :). Она в полной мере описывает все нововведения и улучшения регулярных выражений. Здесь же отмечу, что Microsoft не только подняли производительность, но и добавили различные функциональные улучшения.

Заключение

Как видно, улучшений в .NET 7 довольно много. Не все из них одинаково полезны для всех разработчиков, но многие технологии продолжат своё развитие в рамках будущих выпусков .NET.

Лично для себя могу выделить как наиболее интересные и полезные:

  • естественно, C# 11;
  • on stack replacement (OSR);
  • централизованное управление пакетами (CPM);
  • GC Regions.

Естественно, в статье были перечислены не все новшества, а только самые интересные (по нашему мнению). Со всеми улучшениями вы можете ознакомиться здесь.

Используете ли вы уже что-то из фишек нового .NET? Пишите в комментариях.