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

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

c80d23f55ddf171ae900e172bee6b149.png

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. В итоге получаются следующие результаты:

Method

Runtime

Length

Mean

Min

.NET 6.0

4

26.167 ns

Min

.NET 7.0

4

4.788 ns

Max

.NET 6.0

4

25.236 ns

Max

.NET 7.0

4

4.234 ns

Min

.NET 6.0

1024

3,987.102 ns

Min

.NET 7.0

1024

101.830 ns

Max

.NET 6.0

1024

3,798.069 ns

Max

.NET 7.0

1024

100.279 ns

Подробнее про улучшения производительности 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? Пишите в комментариях.

Если хотите поделиться этой статьей с англоязычной аудиторией, то прошу использовать ссылку на перевод: Artem Rovenskii. What 's new in .NET 7?.

© Habrahabr.ru