Новые возможности .NET 9

Узнайте о новых возможностях .NET 9 и найдите ссылки на дополнительную документацию.

.NET 9, преемник .NET 8, имеет особое внимание на облачных приложениях и производительности. Он будет поддерживаться в течение 18 месяцев в качестве стандартного выпуска поддержки (STS). Вы можете скачать .NET 9 здесь.

Новые возможности для .NET 9, команда инженеров публикует обновления предварительной версии .NET 9 в обсуждениях GitHub. Это отличное место, чтобы задавать вопросы и предоставлять отзывы о выпуске.

Эта статья была обновлена для .NET 9 ( предварительная версия 2). В следующих разделах описываются обновления основных библиотек .NET в .NET 9.

Среда выполнения .NET

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

В System.Text.Json.NET 9 есть новые параметры сериализации JSON и нового одноэлемента, что упрощает сериализацию с помощью веб-значений по умолчанию.

Параметры отступа

JsonSerializerOptions включает новые свойства, позволяющие настроить символ отступа и размер отступа записанного JSON.

var options = new JsonSerializerOptions
{
    WriteIndented = true,
    IndentCharacter = '\t',
    IndentSize = 2,
};

string json = JsonSerializer.Serialize(
    new { Value = 1 },
    options
    );
Console.WriteLine(json);
//{
//                "Value": 1
//}

Параметры веб-сайта по умолчанию

Если вы хотите сериализовать параметры по умолчанию, которые ASP.NET Core используются для веб-приложений, используйте новый JsonSerializerOptions.Web синглтон.

string webJson = JsonSerializer.Serialize(
    new { SomeValue = 42 },
    JsonSerializerOptions.Web // Defaults to camelCase naming policy.
    );
Console.WriteLine(webJson);
// {"someValue":42}

LINQ

Появились новые методы CountBy и AggregateBy были введены. Эти методы позволяют агрегировать состояние по ключу без необходимости выделения промежуточных групп с помощью GroupBy.

CountBy позволяет быстро вычислять частоту каждого ключа. Следующий пример находит слово, которое чаще всего встречается в текстовой строке.

string sourceText = """
    Lorem ipsum dolor sit amet, consectetur adipiscing elit.
    Sed non risus. Suspendisse lectus tortor, dignissim sit amet, 
    adipiscing nec, ultricies sed, dolor. Cras elementum ultrices amet diam.
""";

// Find the most frequent word in the text.
KeyValuePair<string, int> mostFrequentWord = sourceText
    .Split(new char[] { ' ', '.', ',' }, StringSplitOptions.RemoveEmptyEntries)
    .Select(word => word.ToLowerInvariant())
    .CountBy(word => word)
    .MaxBy(pair => pair.Value);

Console.WriteLine(mostFrequentWord.Key); // amet

AggregateBy позволяет реализовать более общие рабочие процессы. В следующем примере показано, как вычислить оценки, связанные с заданным ключом.

(string id, int score)[] data =
    [
        ("0", 42),
        ("1", 5),
        ("2", 4),
        ("1", 10),
        ("0", 25),
    ];

var aggregatedData =
    data.AggregateBy(
        keySelector: entry => entry.id,
        seed: 0,
        (totalScore, curr) => totalScore + curr.score
        );

foreach (var item in aggregatedData)
{
    Console.WriteLine(item);
}
//(0, 67)
//(1, 15)
//(2, 4)

Index<TSource>(IEnumerable<TSource>) позволяет быстро извлечь неявный индекс перечисленного. Теперь можно написать код, например следующий фрагмент кода, чтобы автоматически индексировать элементы в коллекции.

IEnumerable<string> lines2 = File.ReadAllLines("output.txt");
foreach ((int index, string line) in lines2.Index())
{
    Console.WriteLine($"Line number: {index + 1}, Line: {line}");
}

Коллекции

Тип PriorityQueue<TElement,TPriority> коллекции в System.Collections.Generic пространстве имен включает новый Remove(TElement, TElement, TPriority, IEqualityComparer<TElement>) метод, который можно использовать для обновления приоритета элемента в очереди.

Метод PriorityQueue.Remove()

.NET 6 представила коллекцию PriorityQueue<TElement,TPriority> , которая обеспечивает простую и быструю реализацию массива-кучи. Одна из проблем с кучами массивов в целом заключается в том, что они не поддерживают обновления приоритета, что делает их запрещенными для использования в алгоритмах, таких как вариации алгоритма Dijkstra.

Хотя невозможно реализовать эффективные обновления приоритета $O(\log n)$ в существующей коллекции, новый PriorityQueue<TElement,TPriority>.Remove(TElement, TElement, TPriority, IEqualityComparer<TElement>) метод позволяет эмулировать обновления приоритета (хотя и в $O(n)$ времени):

public static void UpdatePriority<TElement, TPriority>(
    this PriorityQueue<TElement, TPriority> queue,
    TElement element,
    TPriority priority
    )
{
    // Scan the heap for entries matching the current element.
    queue.Remove(element, out _, out _);
    // Re-insert the entry with the new priority.
    queue.Enqueue(element, priority);
}

Этот метод разблокирует пользователей, которые хотят реализовать алгоритмы графа в контекстах, где асимптотическая производительность не является блокировщиком. (Такие контексты включают образование и прототип.) Например, вот реализация алгоритма Dijkstra, использующего новый API.

Шифрование

Для шифрования .NET 9 добавляет новый метод хэш-снимка для CryptographicOperations типа. Он также добавляет новые классы, использующие алгоритм KMAC.

Метод CryptographicOperations.HashData()

.NET включает несколько статических реализаций хэш-функций и связанных функций. Эти API включают SHA256.HashData и HMACSHA256.HashData. Api с одним снимком предпочтительнее использовать, так как они могут обеспечить оптимальную производительность и уменьшить или исключить выделение.

Если разработчик хочет предоставить API, поддерживающий хэширование, где вызывающий определяет используемый хэш-алгоритм, он обычно выполняется путем принятия аргумента HashAlgorithmName . Однако использование этого шаблона с интерфейсами API с одним снимком потребует переключения на все возможные HashAlgorithmName , а затем с помощью соответствующего метода. Чтобы решить эту проблему, .NET 9 представляет CryptographicOperations.HashData API. Этот API позволяет создавать хэш или HMAC через входные данные в виде одного снимка, где используемый алгоритм определяется HashAlgorithmName.

static void HashAndProcessData(HashAlgorithmName hashAlgorithmName, byte[] data)
{
    byte[] hash = CryptographicOperations.HashData(hashAlgorithmName, data);
    ProcessHash(hash);
}

Алгоритм KMAC

.NET 9 предоставляет алгоритм KMAC, указанный NIST SP-800-185. Код проверки подлинности сообщений KECCAK (KMAC) — это псевдорандомная функция и хэш-функция с ключом на основе KECCAK.

Следующие новые классы используют алгоритм KMAC. Используйте экземпляры для аккумулирования данных для создания MAC или использования статического HashData метода для однократного ввода .

KMAC доступен в Linux с OpenSSL 3.0 или более поздней версии, а также в Windows 11 сборки 26016 или более поздней версии. Вы можете использовать статическое IsSupported свойство, чтобы определить, поддерживает ли платформа нужный алгоритм.

if (Kmac128.IsSupported)
{
    byte[] key = GetKmacKey();
    byte[] input = GetInputToMac();
    byte[] mac = Kmac128.HashData(key, input, outputLength: 32);
}
else
{
    // Handle scenario where KMAC isn't available.
}

Отражение

В версиях .NET Core и .NET 5-8 поддержка создания сборки и создания метаданных отражения для динамически созданных типов была ограничена выполнением AssemblyBuilder. Отсутствие поддержки сохранения сборки часто является блокировщиком для клиентов, переносящихся с платформа .NET Framework в .NET. .NET 9 добавляет общедоступные API для AssemblyBuilder сохранения генерируемой сборки.

Новая, сохраняемая AssemblyBuilder реализация является средой выполнения и платформы независимой от платформы. Чтобы создать сохраненный AssemblyBuilder экземпляр, используйте новый AssemblyBuilder.DefinePersistedAssembly API. Существующий AssemblyBuilder.DefineDynamicAssembly API принимает имя сборки и необязательные настраиваемые атрибуты. Чтобы использовать новый API, передайте основную сборку, System.Private.CoreLibкоторая используется для ссылки на базовые типы среды выполнения. Нет возможности AssemblyBuilderAccess. И в настоящее время сохраняемая AssemblyBuilder реализация поддерживает только сохранение, а не выполнение. После создания экземпляра сохраняемого AssemblyBuilderэкземпляра последующие шаги по определению модуля, типа, метода или перечисления, записи IL и всех остальных использования остаются неизменными. Это означает, что для сохранения сборки можно использовать существующий System.Reflection.Emit код как есть. Следующий код показывает пример.

public void CreateAndSaveAssembly(string assemblyPath)
{
    AssemblyBuilder ab = AssemblyBuilder.DefinePersistedAssembly(
        new AssemblyName("MyAssembly"),
        typeof(object).Assembly
        );
    TypeBuilder tb = ab.DefineDynamicModule("MyModule")
        .DefineType("MyType", TypeAttributes.Public | TypeAttributes.Class);

    MethodBuilder mb = tb.DefineMethod(
        "SumMethod",
        MethodAttributes.Public | MethodAttributes.Static,
        typeof(int), [typeof(int), typeof(int)]
        );
    ILGenerator il = mb.GetILGenerator();
    il.Emit(OpCodes.Ldarg_0);
    il.Emit(OpCodes.Ldarg_1);
    il.Emit(OpCodes.Add);
    il.Emit(OpCodes.Ret);

    tb.CreateType();
    ab.Save(assemblyPath); // or could save to a Stream
}

public void UseAssembly(string assemblyPath)
{
    Assembly assembly = Assembly.LoadFrom(assemblyPath);
    Type type = assembly.GetType("MyType");
    MethodInfo method = type.GetMethod("SumMethod");
    Console.WriteLine(method.Invoke(null, [5, 10]));
}

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

.NET 9 включает усовершенствования 64-разрядного компилятора JIT, направленного на повышение производительности приложения. К этим усовершенствованиям компилятора относятся следующие:

Векторизация Arm64 — это еще одна новая функция среды выполнения.

Оптимизация циклов

Улучшение создания кода для циклов является приоритетом для .NET 9, и 64-разрядный компилятор имеет новую оптимизацию, называемую индукционной переменной (IV).

IV — это переменная, значение которой изменяется в виде итерации цикла. В следующем for цикле i используется IV: for (int i = 0; i < 10; i++) Если компилятор может проанализировать, как значение IV развивается по итерации цикла, он может создать более производительный код для связанных выражений.

Рассмотрим следующий пример, который выполняет итерацию по массиву:

static int Sum(int[] arr)
{
    int sum = 0;
    for (int i = 0; i < arr.Length; i++)
    {
        sum += arr[i];
    }

    return sum;
}

Переменная индекса — i4 байта. На уровне сборки 64-разрядные регистры обычно используются для хранения индексов массива в x64, а в предыдущих версиях .NET компилятор создал код, который ноль расширен i до 8 байт для доступа к массиву, но продолжал рассматриваться i как целое число 4-байтов в другом месте. Однако для i расширения до 8 байт требуется дополнительная инструкция по x64. При расширении IV 64-разрядный компилятор JIT теперь расширяет i до 8 байтов в цикле, пропуская нулевое расширение. Циклирование по массивам очень распространено, и преимущества этого удаления инструкций быстро добавляются.

Усовершенствования встраивание для собственного AOT

Один из . Цели NET для 64-разрядной встроенной модели JIT-компилятора — удалить как можно больше ограничений, которые блокируют встраивать метод. .NET 9 позволяет встраивать доступ к локальным потокам в Windows x64, Linux x64 и Linux Arm64.

Для static членов класса один экземпляр элемента существует во всех экземплярах класса, которые "совместно используют" член. Если значение static элемента уникально для каждого потока, то это значение, локальное значение может повысить производительность, так как это устраняет необходимость примитива параллелизма для безопасного доступа к static элементу из содержащего потока.

Ранее доступ к локальным потокам в программах, скомпилированных в машинном коде AOT, требовал 64-разрядного компилятора JIT для вызова среды выполнения, чтобы получить базовый адрес локального хранилища потока. Теперь компилятор может встраивать эти вызовы, что приводит к гораздо меньшему числу инструкций для доступа к этим данным.

Улучшения PGO: тип проверка и приведения

По умолчанию в .NET 8 включена динамическая оптимизация по профилю (PGO ). NET 9 расширяет 64-разрядную реализацию PGO компилятора JIT, чтобы профилировать дополнительные шаблоны кода. Если включена многоуровневая компиляция, 64-разрядный компилятор JIT уже вставляет инструментирование в программу для профилирования его поведения. При повторной компиляции с оптимизацией компилятор использует профиль, созданный во время выполнения, чтобы принимать решения, относящиеся к текущему запуску программы. В .NET 9 64-разрядный компилятор JIT использует данные PGO для повышения производительности типов проверка.

Для определения типа объекта требуется вызов среды выполнения, который поставляется с штрафом производительности. Если тип объекта должен быть проверка, 64-разрядный компилятор JIT выдает этот вызов для правильности (компиляторы обычно не могут исключить какие-либо возможности, даже если они кажутся невероятными). Однако если данные PGO свидетельствуют о том, что объект, скорее всего, будет определенным типом, то 64-разрядный компилятор JIT теперь выдает быстрый путь, который дешево проверка для этого типа, и возвращается на медленный путь вызова в среду выполнения, только если это необходимо.

Векторизация Arm64 в библиотеках .NET

Новая EncodeToUtf8 реализация использует 64-разрядную возможность JIT-компилятора выдавать инструкции по многорегистрированной загрузке и хранилищу в Arm64. Это поведение позволяет программам обрабатывать большие фрагменты данных с меньшим количеством инструкций. Приложения .NET в различных доменах должны видеть улучшения пропускной способности оборудования Arm64, поддерживающего эти функции. Некоторые тесты сокращают время выполнения более чем на половину.

Пакет SDK для .NET

Модульное тестирование

В этом разделе описываются обновления модульного тестирования в .NET 9: параллельное выполнение тестов и выходные данные теста средства ведения журнала терминалов.

Параллельное выполнение тестов

В .NET 9 dotnet test более полностью интегрирована с MSBuild. Так как MSBuild поддерживает параллельное создание, можно выполнять тесты для одного и того же проекта в разных целевых платформах параллельно. По умолчанию MSBuild ограничивает число параллельных процессов числом процессоров на компьютере. Вы также можете задать собственный предел с помощью переключателя -maxcpucount . Если вы хотите отказаться от параллелизма, задайте TestTfmsInParallel для свойства MSBuild значение false.

Отображение теста средства ведения журнала терминала

Отчеты dotnet test о результатах теста теперь поддерживаются непосредственно в средстве ведения журнала терминала MSBuild. Вы получаете более полнофункциональный отчет о тестах как во время выполнения тестов (отображается имя тестов), так и после завершения тестов (все ошибки теста отображаются лучше).

Дополнительные сведения о средстве ведения журнала терминала см. в параметрах сборки dotnet.

Перенаправка средства .NET

Средства .NET — это приложения, зависящие от платформы, которые можно устанавливать глобально или локально, а затем запускать с помощью пакета SDK для .NET и установленных сред выполнения .NET. Эти средства, как и все приложения .NET, предназначены для конкретной основной версии .NET. По умолчанию приложения не выполняются в более новых версиях .NET. Авторы инструментов смогли принять участие в запуске своих средств в более новых версиях среды выполнения .NET, задав RollForward свойство MSBuild. Однако не все инструменты делают это.

Новый вариант dotnet tool install позволяет пользователям решать, как должны выполняться средства .NET. При установке средства через dotnet tool installdotnet tool run <toolname>или при запуске средства можно указать новый флаг--allow-roll-forward. Этот параметр настраивает средство с режимом Majorпереката. Этот режим позволяет средству работать в более новой основной версии .NET, если соответствующая версия .NET недоступна. Эта функция помогает ранним пользователям использовать средства .NET без необходимости изменять любой код.

См. также