Мы используем куки, чтобы пользоваться сайтом было удобно.
Хорошо
to the top

Вебинар: Безопасная разработка и сертификация: практика, инструменты, перспективы - 03.12

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

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

10 Ноя 2026

Совсем недавно вышел .NET 11, и это значит, что пора обновлять свои проекты. Но сначала посмотрим, стоят ли нововведения в C# 15, Runtime Async, JIT-оптимизации и многое другое того, чтобы спешить с переходом на новую версию.

.NET 11 фокусируется на улучшении производительности и Runtime. Сам же релиз является standard-term support (STS), а поддерживаться будет два года. Напомню, что, начиная с .NET 9, срок поддержки STS-релизов увеличили с 18 до 24 месяцев.

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

C# 15

Все нововведения C# 15 были разобраны в отдельной статье. К примеру, в язык были добавлены union, закрытые иерархии, индексаторы расширений, был переработан подход к безопасности памяти. Изменения в этом году получились довольно интересными и крупными. А что бы вы сказали о новой версии языка?

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

Уже по традиции каждый год Стивен Тауб выпускает статью с обзором улучшений производительности в новом .NET, где подробно разбирает и описывает все изменения.

В этом году были затронуты JIT, Runtime Async, девиртуализация, проверки границ и ещё множество вещей.

Всем заинтересованным рекомендуется посмотреть статью Стивена Тауба, чтобы увидеть полноценный разбор именно этой части .NET 11.

Библиотеки

Сжатие

Теперь пространство имён System.IO.Compression включает в себя сжатие через Zstandard (ZstandardStream, ZstandardEncoder и другие). При чтении ZIP-архивов теперь проверяется контрольная сумма (CRC32), поэтому повреждённый архив выдаст исключение InvalidDataException. Также у записей ZIP-архива появилась поддержка паролей и открытие с указанием FileAccess. Для энкодеров и декодеров Deflate, ZLib, GZip был добавлен метод Reset(), который позволяет сбросить состояние экземпляра и переиспользовать его, а не создавать новый объект.

LINQ

Новая версия приносит улучшения для существующих методов Join, а также добавляет метод FullJoin, который возвращает все элементы из обеих последовательностей, объединяя по ключу совпадающие элементы и сохраняя все комбинации, включая отсутствие пары как с левой, так и с правой сторон. Посмотрим на FullJoin в деле:

var products = new List<(string Name, string? Category)>
{
  ("Laptop", "Electronics"),
  ("Table", null)
};
var categories = new List<(string Name, string Description)>
{
  ("Electronics", "Electronic devices"),
  ("Furniture", "Office furniture")
};

var joined = products.FullJoin(categories, p => p.Category, c => c.Name);
foreach (var (product, category) in joined)
    Console.WriteLine($"{product.Name ?? "(none)"} 
    : {category.Description ?? "(none)"}");

Получаем такой вывод:

Laptop: Electronic devices
Table: (none)
(none): Office furniture

В дополнение к этому LINQ позволил использовать Join и GroupJoin без указания resultSelector, чтобы сразу возвращать кортежи, как это делают LeftJoin, RightJoin и FullJoin.

JSON

Расширились возможности System.Text.Json. Например, для новой структуры union, добавленной в C# 15, были адаптированы методы сериализации и десериализации: значение текущего типа объединения записывается напрямую, без обёртки и дискриминатора. Также появилась запись в формате JSON Lines (JSONL), где каждое значение находится на отдельной строке. Для этого у нового метода JsonSerializer.SerializeAsyncEnumerable есть параметр topLevelValues.

У JsonPolymorphicAttribute появилось новое свойство InferClosedTypePolymorphism, которое позволяет включать автоматический вывод производных типов для одной закрытой иерархии, не меняя при этом глобальные настройки сериализатора. Ранее каждый производный тип нужно было явно регистрировать атрибутом JsonDerivedType на базовом типе. При желании эту настройку можно задать и глобально: через JsonSerializerOptions или опции генератора исходного кода. В .NET 11 это выглядит так:

[JsonPolymorphic(InferClosedTypePolymorphism = true)]
closed record Shape;
record Circle(double Radius) : Shape;
record Square(double Length) : Shape;

Добавлены generic-методы GetTypeInfo<T>() и TryGetTypeInfo<T>(), возвращающие JsonTypeInfo<T>. Также появились встроенная политика именования JsonNamingPolicy.PascalCase, атрибут [JsonNamingPolicy] для типов и их членов, возможность указать [JsonIgnore] сразу для всего типа и встроенные конвертеры для BFloat16, Decimal32, Decimal64 и Decimal128.

Асинхронная валидация

Стала доступна асинхронная валидация с помощью AsyncValidationAttribute и IAsyncValidatableObject, а также новых методов Validator.ValidateObjectAsync, Validator.TryValidateObjectAsync и других. Это пригодится для случаев, когда для проверки нужно обратиться к базе данных или внешнему сервису, не блокируя поток. В Microsoft.Extensions.Options асинхронно можно валидировать и опции, в том числе при старте приложения через новый интерфейс IAsyncStartupValidator.

Новые типы потоков

Добавлены четыре новых типа Stream: ReadOnlyMemoryStream, WritableMemoryStream, ReadOnlySequenceStream и StringStream. Они позволяют без лишнего копирования передать уже имеющийся буфер или строку в API, принимающее Stream. Соответственно, уменьшится число аллокаций и нагрузка на GC.

Числовые типы и парсинг

В System.Numerics появились типы Decimal32, Decimal64 и Decimal128. Это десятичные числа с плавающей точкой по стандарту IEEE 754-2019 с точностью до 7, 16 и 34 значащих цифр соответственно. Как и System.Decimal, они хранят число в десятичной системе, поэтому, например, 0.1 в них представляется точно. Но в отличие от System.Decimal, у которого собственный формат без NaN и бесконечностей, новые типы следуют стандарту, то есть поддерживают NaN, бесконечности и generic math. Пригодятся они там, где нужно именно поведение IEEE 754, например для совместимости с другими языками и СУБД, использующими этот формат. Заодно появился и обобщённый Complex<T>, который работает с float, Half, BFloat16 и новыми десятичными типами.

Был добавлен новый метод INumberBase<TSelf>.TryParsePartial, который даёт возможность распарсить число до первого символа, не являющегося частью числа. То есть если раньше нам бы попалось число вроде 123;789, то TryParse вернул бы false, а в результат попал бы 0. Посмотрим на примере, как это будет выглядеть теперь:

public static void ParseTest()
{
  double.TryParsePartial("123;789", 
                          System.Globalization.NumberStyles.Number, 
                          null, out var result, out var consumed);
  Console.WriteLine($"Parsed value (TryParsePartial): {result}");
  Console.WriteLine($"Consumed: {consumed}");

  double.TryParse("456;032", null, out result);
  Console.WriteLine($"Parsed value (TryParse): {result}");
}

Вывод:

Parsed value (TryParsePartial): 123
Consumed: 3
Parsed value (TryParse): 0

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

Процессы

У класса Process появился целый набор новых API. Раньше, чтобы просто запустить утилиту и забрать её вывод, приходилось подписываться на OutputDataReceived и ErrorDataReceived или вручную вычитывать потоки. Теперь это делается одним вызовом:

ProcessTextOutput result = await Process.RunAndCaptureTextAsync("git", 
                           ["status", "--porcelain"]);

Console.WriteLine(result.StandardOutput);
Console.WriteLine($"Exit code: {result.ExitStatus.ExitCode}");

Также были добавлены следующие методы:

Runtime

Runtime Async

Крупное изменение Runtime в этом году. Стоит вспомнить, что для каждого async-метода компилятор генерирует конечный автомат (state machine), а Runtime Async перекладывает эту работу на сам Runtime. В .NET 10 это был эксперимент, а в .NET 11 функция по-прежнему помечена как preview, но для проектов под .NET 11 больше не требует EnablePreviewFeatures. Более того, сами библиотеки .NET уже скомпилированы с Runtime Async и не содержат сгенерированных автоматов. Для своего проекта функцию нужно включить явно:

<PropertyGroup>
  <Features>runtime-async=on</Features>
</PropertyGroup>

Что это даёт? Во-первых, меньше накладных расходов, так как JIT научился сворачивать Task.FromResult, Task.CompletedTask и ValueTask.FromResult в более быстрые пути, переиспользовать объекты продолжений и применять многоуровневую компиляцию к горячим асинхронным методам. Во-вторых, стек вызовов, получаемый через new StackTrace(), стал более читаемым, так как теперь туда выводится только реальная цепочка вызовов, исключая служебные. Отладка тоже стала проще, в том числе благодаря тому, что шаг через await больше не уводит в инфраструктурный код.

Новые требования к процессору

Это изменение может сломать запуск приложения на старом железе. На x86/x64 минимальным уровнем для JIT и NativeAOT стал x86-64-v2 вместо x86-64-v1. На процессоре, который не дотягивает до этого уровня, приложение просто не запустится и сообщит о нехватке базовых наборов инструкций.

Кроме того, образы ReadyToRun под Windows и Linux на x64 теперь ориентированы на x86-64-v3 (AVX2, BMI2, FMA и другие). На процессорах без этих инструкций приложение запустится, но предкомпилированный код придётся перекомпилировать через JIT, что замедлит старт. Такие процессоры сегодня ещё нужно поискать, но лучше проверить заранее, на каком оборудовании работает ваш проект.

Улучшения JIT

Помимо поддержки Runtime Async, JIT научился делать ряд полезных вещей. Теперь он умеет убирать больше проверок границ, в том числе для обращений вида values[^1] и i + const < length, а также для span[0] после проверки !span.IsEmpty. Может вычислять на этапе компиляции string.Equals и SequenceEqual, когда оба значения известны. Также он удаляет избыточные условия и ветвления и генерирует инструкцию MUL для Math.BigMul на x64.

NativeAOT и ReadyToRun

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

Comparer<T>.Default и EqualityComparer<T>.Default при предкомпиляции теперь специализируются в ReadyToRun, поэтому им больше не нужен JIT.

SDK

CLI стал быстрее

CLI dotnet теперь по умолчанию работает как NativeAOT-приложение. Команды вроде dotnet --version, dotnet --info и dotnet --help выполняются полностью нативно, а внешние инструменты, например dotnet ef, запускаются в обход старта управляемой части CLI.

Установщики SDK для Linux и macOS стали меньше: одинаковые сборки теперь заменяются символическими ссылками на одну копию.

Приложения из одного файла

Продолжает развиваться появившийся в .NET 10 запуск .cs-файлов без проекта. Директива #:include позволяет разнести код по нескольким файлам и даже подключить готовую DLL.

Кроме того, такие приложения теперь поддерживают dotnet format, а повторный запуск неизменённого приложения с NativeAOT переиспользует уже собранный результат.

dotnet run и dotnet watch

dotnet run научился передавать приложению переменные окружения прямо из командной строки с помощью параметра -e.

dotnet watch получил интеграцию с хостом Aspire, автоматический перезапуск после падения и выбор устройства для MAUI-проектов.

Тестирование

Много новых опций получил dotnet test в Microsoft.Testing.Platform. Например, теперь можно ограничить время прогона или остановиться после определённого числа упавших тестов.

Также появилась опция --no-dependencies, позволяющая не собирать зависимые проекты. Добавлен экспериментальный режим запуска только тех тестов, которые затронуты изменениями.

Заключение

.NET 11 получился релизом, в первую очередь, про Runtime: Runtime Async уже используется в самих библиотеках .NET, JIT стал ещё умнее, а CLI — быстрее. В повседневной же работе заметнее всего будут нововведения C# 15 и небольшие, но приятные методы вроде Process.RunAndCaptureTextAsync, FullJoin и TryParsePartial.

Если планируете переход, то не забудьте проверить новые требования к процессорам.

Полный список изменений можно найти в документации Microsoft. А какие нововведения понравились вам? Пишите в комментариях.

Если вам понравилась статья, то в нашем блоге вы найдёте ещё больше интересного. А подписаться на новые публикации можно здесь.

Подписаться на рассылку
Хотите раз в месяц получать от нас подборку вышедших в этот период самых интересных статей и новостей? Подписывайтесь!
Популярные статьи по теме

Комментарии (0)

Следующие комментарии next comments
close comment form