Microsoft выпустила .NET 10, версию с долгосрочной поддержкой (LTS), которая будет актуальна до 10 ноября 2028 года. Платформа получила значительные улучшения производительности в каждом компоненте: от рантайма и языков программирования до системных библиотек и инструментов сборки.
Также компания Microsoft советует разработчикам перейти на .NET 10, чтобы ускорить выполнение и снизить энергопотребление приложений. Для облегчения миграции можно использовать GitHub Copilot, который поможет адаптировать существующий код и обновить зависимости.
Выход .NET 10 принёс не революцию, но массу точечных, зрелых улучшений. Команда разработчиков платформы явно взяла курс на «полировку»: меньше экспериментальных фич, больше производительности, удобства и чистоты API. Разбираем главное в этой статье.
Новая версия языка делает синтаксис более гибким без потери читаемости.
Свойства с field. Теперь можно писать кастомные get/set, обращаясь к резервному полю через контекстное ключевое слово field. Никаких лишних полей вручную.
public string Name { get => field; set => field = value.Trim(); }
Начиная с C# 14 аргумент nameof может быть несвязанным универсальным типом. Например, nameof(List<>) принимает значение List. В более ранних версиях C# для возврата List<int> имени можно использовать только закрытые универсальные типы, например List.
Изменение поможет сделать код более оптимизированным с меньшими усилиями. C# научился проводить больше неявных преобразований, чтобы нам было приятнее использовать Span в работе.
Span — это тип, который позволяет эффективно управлять памятью. Мы как бы создаём «окно», через которое взаимодействуем с памятью. А ещё Span не допустит выхода за границы выделенного «окна» памяти, как это могло бы произойти при небезопасном обращении к ней. Он сам проверит и выдаст ошибку, если индекс выходит за границы.
C# 14 распознает связь и поддерживает некоторые преобразования между ReadOnlySpan<T>, Span<T>и T[]. Типы span могут выступать в качестве получателей методов расширения, комбинироваться с другими преобразованиями и помогать в ситуациях вывода универсальных типов.
Параметры вроде ref, in, out теперь можно указывать даже в коротких лямбдах:
var tryParse = (ref int x) => int.TryParse("42", out x);
Новое решение даст нам возможность сосредоточиться на разработке кода, а не на многократном описании стандартных процедур. Особенно это будет актуально для out-параметров.
Ранее C# 13 уже разрешил применение partial для свойств и индексаторов. В новой же версии partial можно использовать для разделения конструкторов и ивентов.
Наибольшую выгоду от partial функции получат создатели библиотек и инструментов для генерации кода. Частичные события окажутся особенно полезны для библиотек, поддерживающих слабые события, а частичные конструкторы — для платформ, генерирующих совместимый код.
Важно отметить, что в отличие от методов, которые могут быть только объявлены, частичные конструкторы и события должны иметь полную реализацию.
Это оптимизационная функция, которая расширяет возможности перегрузки операторов в языке программирования. Она позволяет перегружать не только унарные и бинарные операторы, но и составные операторы, такие как +=, *= и другие. Поскольку реализация этих операторов имеет много общего, в дальнейшем тексте будет рассматриваться оператор +=. Однако все выводы, сделанные для него, будут применимы и к аналогичным операторам.
Важно отметить, что при использовании оператора += в языке C# 13 сначала выполняется вызов перегруженного оператора +, а затем происходит присваивание результата.
При работе со значимыми типами перегрузка оператора + приводит к дополнительному копированию обоих операндов и созданию нового экземпляра в качестве результата. Хотя такое поведение ожидаемо, оно может вызывать неоправданные накладные расходы на копирование и обработку данных, особенно при работе с крупными типами, такими как математические векторы и тензоры.
Больше не нужно конвертировать в DateOnly в DateTime только ради номера недели, актуально для бизнес-приложений с интенсивной обработкой дат.
public static class ISOWeek { public static int GetWeekOfYear(DateOnly date); public static int GetYear(DateOnly date); public static DateOnly ToDateOnly(int year, int week, DayOfWeek dayOfWeek); }
Режим обновления архивов больше не загружает всё в память. Распаковка — параллельная. Особенно заметно на больших ZIP-файлах.
Поиск сертификатов по отпечатку стал безопаснее благодаря новому методу FindByThumbprint, который позволяет явно указывать современный алгоритм хеширования (например, SHA-256), отказываясь от устаревшего SHA-1.
Обработка PEM-файлов оптимизирована для работы с байтовыми представлениями UTF-8. Новый метод PemEncoding.FindUtf8 устраняет необходимость преобразования байт в строку, что снижает потребление памяти и ускоряет парсинг.
// До .NET 10 byte[] fileContents = File.ReadAllBytes(path); char[] text = Encoding.ASCII.GetString(fileContents); PemFields pemFields = PemEncoding.Find(text); // .NET 10 byte[] fileContents = File.ReadAllBytes(path); PemFields pemFields = PemEncoding.FindUtf8(fileContents);
Главное нововведение — улучшенная поддержка асинхронных операций. API для отображения форм и диалогов, которые были экспериментальными в .NET 9 и требовали отключения предупреждения компилятора WFO5002, теперь полностью стабильны и готовы к использованию без каких-либо дополнительных настроек. Методы Form.ShowAsync, Form.ShowDialogAsync и TaskDialog.ShowDialogAsync позволяют создавать по-настоящему отзывчивый интерфейс без блокировок, особенно при работе с несколькими окнами.
В .NET 10 в состав платформы перенесены недостающие типы редакторов из .NET Framework, связанные с UITypeEditor. Теперь они доступны при работе с PropertyGrid и панелью действий конструктора. Также исправлена работа SnapLines для пользовательских конструкторов.
В ответ на современные требования безопасности добавлен API для предотвращения нежелательной записи экрана. Свойство Form.ScreenCaptureMode позволяет гибко управлять видимостью формы при захвате: скрывать содержимое, полностью размывать окно или оставить всё как есть. Это особенно актуально для приложений, работающих с конфиденциальными данными.
Ключевое улучшение производительности коснулось элементов управления CollectionView и CarouselView на iOS и Mac Catalyst. Оптимизированные обработчики, которые в .NET 9 были доступны как опциональная функция, теперь включены по умолчанию. Это обеспечивает более высокую скорость работы и стабильность при отображении сложных списков с кастомными шаблонами, снижая потребление ресурсов.
Теперь использует оптимизированный обработчик по умолчанию.
Компонент HybridWebView получил несколько значительных улучшений для более тесной интеграции веб-кода и .NET:
События жизненного цикла — появились события WebViewInitializing и WebViewInitialized, позволяющие настраивать веб-представление на уровне платформы до и после его создания.
Значимым нововведением стала возможность перехватывать запросы внутри BlazorWebView и HybridWebView. Разработчики могут изменять заголовки, перенаправлять трафик или подменять ответы, что открывает широкие возможности для кэширования, безопасности и локальной обработки данных.
{ if (e.Uri.ToString().Contains("api/secure")) { e.Handled = true; e.SetResponse(200, "OK", "application/json", GetCustomStream()); } };
В Blazor-приложениях потоковая передача ответов для HttpClient теперь включена по умолчанию. Это значит, что метод response.Content.ReadAsStreamAsync() возвращает BrowserHttpReadStream вместо MemoryStream, что снижает потребление памяти, но требует внимания при использовании синхронных операций.
Для сохранения прежнего поведения предусмотрены варианты отключения:
<WasmEnableStreamingResponse>false</WasmEnableStreamingResponse>
HttpRequestMessage.SetBrowserResponseStreamingEnabled(false)
В .NET 10 улучшена загрузка ресурсов фреймворка для обеих моделей Blazor-приложений:
OverrideHtmlAssetPlaceholders в true и добавить в <head> элемент <link rel="preload" id="webassembly" />.
Теперь разработчики могут не только вызывать функции, но и полноценно работать с объектами и их свойствами:
Эти возможности доступны как в асинхронном (IJSRuntime), так и в синхронном (IJSInProcessRuntime) режимах, открывая новые сценарии для тесной интеграции .NET и JavaScript-кода в гибридных и Blazor-приложениях.
// Пример создания экземпляра JS-класса и работы с ним var classRef = await JSRuntime.InvokeConstructorAsync("jsInterop.TestClass", "Blazor!"); var text = await classRef.GetValueAsync<string>("text"); var textLength = await classRef.InvokeAsync<int>("getTextLength");
.NET 10 — это релиз без «вау», но с глубокой полировкой. Язык стал выразительнее, WinForms — современнее, MAUI — стабильнее, а Blazor — ещё ближе к «родной» работе с браузером. Обновляться стоит однозначно, даже если вы не гонитесь за новизной: исправления легаси и оптимизации памяти окупаются сами.