Поделиться через


Новые возможности среды выполнения .NET 8

В этой статье описываются новые функции среды выполнения .NET для .NET 8.

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

.NET 8 включает улучшения для создания кода и JIT-компиляции:

  • Улучшения производительности Arm64
  • Улучшения SIMD
  • Поддержка расширений ISA AVX-512 (см . вектор512 и AVX-512)
  • Улучшения на основе облака
  • Улучшения пропускной способности JIT
  • Цикл и общие оптимизации
  • Оптимизированный доступ для полей, помеченных как ThreadStaticAttribute
  • Последовательное выделение регистра. Arm64 содержит две инструкции для поиска вектора таблицы, которые требуют, чтобы все сущности в кортеже операндов присутствовали в последовательных регистрах.
  • JIT/NativeAOT теперь может отменить и автоматически векторизировать некоторые операции памяти с SIMD, например сравнение, копирование и ноль, если он может определить их размеры во время компиляции.

Кроме того, улучшена оптимизация с помощью динамического профиля (PGO) и теперь включена по умолчанию. Для его включения больше не требуется использовать параметр конфигурации среды выполнения. Она работает в тесной связке с многоуровневой компиляцией для дальнейшей оптимизации кода на основе дополнительного инструментария, размещаемого на уровне 0.

В среднем динамический PGO повышает производительность примерно на 15 %. В наборе тестов на 4600 тестов 23% улучшили производительность на 20% или более.

Повышение структуры Codegen

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

JIT уже поддерживает эту оптимизацию, но с несколькими большими ограничениями, в том числе:

  • Он поддерживается только для структур с четырьмя или меньшими полями.
  • Он поддерживается только в том случае, если каждое поле было примитивным типом или простой структурой, упаковав примитивный тип.

Физическое повышение устраняет эти ограничения, что устраняет ряд долгосрочных проблем JIT.

Сбор мусора

.NET 8 добавляет возможность настроить ограничение памяти на лету. Это полезно в сценариях облачной службы, где приходит и идет спрос. Чтобы быть экономически эффективным, службы должны увеличивать и уменьшать потребление ресурсов по мере колебания спроса. Когда служба обнаруживает снижение спроса, она может сократить потребление ресурсов, уменьшая его предел памяти. Ранее это не удалось, так как сборщик мусора (GC) не знал об изменении и может выделить больше памяти, чем новое ограничение. С помощью этого изменения можно вызвать RefreshMemoryLimit() API для обновления GC с новым ограничением памяти.

Существуют некоторые ограничения, которые следует учитывать:

  • На 32-разрядных платформах (например, Windows x86 и Linux ARM) .NET не может установить новое жесткое ограничение кучи, если еще нет.
  • API может вернуть код состояния, отличный от нуля, указывающий на сбой обновления. Это может произойти, если масштабирование слишком агрессивно и не оставляет места для GC маневрировать. В этом случае рассмотрите возможность GC.Collect(2, GCCollectionMode.Aggressive) сокращения текущего использования памяти, а затем повторите попытку.
  • Если увеличить предел памяти, превышающий размер, который GC считает, что процесс может обрабатываться во время запуска, RefreshMemoryLimit вызов завершится успешно, но он не сможет использовать больше памяти, чем то, что он воспринимает как ограничение.

В следующем фрагменте кода показано, как вызвать API.

GC.RefreshMemoryLimit();

Можно также обновить некоторые параметры конфигурации GC, связанные с ограничением памяти. Следующий фрагмент кода задает жесткое ограничение кучи в 100 мебибайт (MiB):

AppContext.SetData("GCHeapHardLimit", (ulong)100 * 1_024 * 1_024);
GC.RefreshMemoryLimit();

API может вызвать InvalidOperationException , если жесткое ограничение недопустимо, например, в случае отрицательного ограничения жесткого ограничения кучи и если жесткое ограничение слишком низко. Это может произойти, если ограничение в куче, которое будет установлено обновление, из-за новых параметров AppData или подразумеваемых изменениями ограничения памяти контейнера, меньше, чем то, что уже зафиксировано.

Глобализация для мобильных приложений

Мобильные приложения в iOS, tvOS и MacCatalyst могут выбрать новый режим гибридной глобализации, использующий более легкий пакет ICU. В гибридном режиме данные глобализации частично извлекается из пакета ICU и частично из вызовов в собственные API. Гибридный режим служит всем языковым стандартам, поддерживаемым мобильными устройствами.

Гибридный режим наиболее подходит для приложений, которые не могут работать в невариантном режиме глобализации и используют язык и региональные параметры, которые были обрезаны из данных ICU на мобильных устройствах. Его также можно использовать при загрузке меньшего файла данных ICU. (Файл icudt_hybrid.dat меньше 34,5 % меньше, чем файл данных ICU по умолчанию icudt.dat.)

Чтобы использовать режим гибридной HybridGlobalization глобализации, задайте для свойства MSBuild значение true:

<PropertyGroup>
  <HybridGlobalization>true</HybridGlobalization>
</PropertyGroup>

Существуют некоторые ограничения, которые следует учитывать:

  • Из-за ограничений собственного API не все API глобализации поддерживаются в гибридном режиме.
  • Некоторые поддерживаемые API имеют другое поведение.

Чтобы проверка, если приложение затронуто, ознакомьтесь с различиями в поведении.

Взаимодействие COM, созданное источником

.NET 8 включает новый генератор источников, поддерживающий взаимодействие с интерфейсами COM. Вы можете пометить GeneratedComInterfaceAttribute интерфейс как COM-интерфейс для исходного генератора. Затем генератор источника создаст код для включения вызова из кода C# в неуправляемый код. Он также создает код для включения вызова неуправляемого кода в C#. Этот генератор источников интегрируется с LibraryImportAttributeи можно использовать типы с GeneratedComInterfaceAttribute параметрами и возвращаемыми типами в LibraryImportметодах-атрибутах.

using System.Runtime.InteropServices;
using System.Runtime.InteropServices.Marshalling;

[GeneratedComInterface]
[Guid("5401c312-ab23-4dd3-aa40-3cb4b3a4683e")]
partial interface IComInterface
{
    void DoWork();
}

internal partial class MyNativeLib
{
    [LibraryImport(nameof(MyNativeLib))]
    public static partial void GetComInterface(out IComInterface comInterface);
}

Генератор источника также поддерживает новый GeneratedComClassAttribute атрибут, позволяющий передавать типы, реализующие интерфейсы с атрибутом в неуправляемый GeneratedComInterfaceAttribute код. Генератор источника создаст код, необходимый для предоставления COM-объекта, реализующего интерфейсы и переадресации вызовов управляемой реализации.

Методы в интерфейсах с GeneratedComInterfaceAttribute атрибутом поддерживают все те же типы, что LibraryImportAttributeи LibraryImportAttribute типы атрибутов. Теперь поддерживаются GeneratedComInterfaceтипы атрибутов и GeneratedComClassатрибуты.

Если код C# использует GeneratedComInterfaceтолько интерфейс-атрибут для оболочки COM-объекта из неуправляемого кода или оболочки управляемого объекта из C# для предоставления неуправляемого кода, можно использовать параметры в Options свойстве для настройки создаваемого кода. Эти параметры означают, что вам не нужно писать маршаллеры для сценариев, которые вы знаете, не будут использоваться.

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

Если вы используете Visual Studio, новые анализаторы и исправления кода упрощают преобразование существующего кода взаимодействия COM для использования исходного взаимодействия. Рядом с каждым интерфейсом, который имеет ComImportAttributeлампочки, предоставляет возможность преобразования в созданный источником взаимодействия. Исправление изменяет интерфейс для использования атрибута GeneratedComInterfaceAttribute . Рядом с каждым классом, реализующим интерфейс с GeneratedComInterfaceAttribute, лампочка предлагает возможность добавить GeneratedComClassAttribute атрибут в тип. После преобразования типов можно переместить DllImport методы для использования LibraryImportAttribute.

Ограничения

Генератор источников COM не поддерживает сходство квартир, используя new ключевое слово для активации COM CoClass и следующих API:

  • IDispatchинтерфейсы на основе.
  • IInspectableинтерфейсы на основе.
  • Свойства и события COM.

Генератор источника привязки конфигурации

.NET 8 представляет генератор источников для предоставления AOT и обрезки конфигурации в ASP.NET Core. Генератор является альтернативой существующей реализации на основе отражения.

Исходный генератор пробы для Configure(TOptions), Bindи Get вызовы для получения сведений о типе из. Если генератор включен в проекте, компилятор неявно выбирает созданные методы для существующих реализаций платформы на основе отражения.

Для использования генератора не требуются изменения исходного кода. Она включена по умолчанию в веб-приложениях AOT. Для других типов проектов генератор исходного кода отключен по умолчанию, но вы можете выбрать EnableConfigurationBindingGenerator это свойство true в файле проекта:

<PropertyGroup>
    <EnableConfigurationBindingGenerator>true</EnableConfigurationBindingGenerator>
</PropertyGroup>

В следующем коде показан пример вызова привязки.

public class ConfigBindingSG
{
    static void RunIt(params string[] args)
    {
        WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
        IConfigurationSection section = builder.Configuration.GetSection("MyOptions");

        // !! Configure call - to be replaced with source-gen'd implementation
        builder.Services.Configure<MyOptions>(section);

        // !! Get call - to be replaced with source-gen'd implementation
        MyOptions? options0 = section.Get<MyOptions>();

        // !! Bind call - to be replaced with source-gen'd implementation
        MyOptions options1 = new();
        section.Bind(options1);

        WebApplication app = builder.Build();
        app.MapGet("/", () => "Hello World!");
        app.Run();
    }

    public class MyOptions
    {
        public int A { get; set; }
        public string S { get; set; }
        public byte[] Data { get; set; }
        public Dictionary<string, string> Values { get; set; }
        public List<MyClass> Values2 { get; set; }
    }

    public class MyClass
    {
        public int SomethingElse { get; set; }
    }
}

Основные библиотеки .NET

В этом разделе содержатся следующие подтопики:

Отражение

Указатели функций были введены в .NET 5, однако соответствующая поддержка отражения не была добавлена в то время. При использовании typeof или отражении указателя функции, например typeof(delegate*<void>()) или FieldInfo.FieldType соответственно, IntPtr возвращается. Начиная с .NET 8 System.Type , возвращается объект. Этот тип предоставляет доступ к метаданным указателя функции, включая соглашения о вызовах, тип возврата и параметры.

Примечание.

Экземпляр указателя функции, который является физическим адресом функции, продолжает представляться как .IntPtr Изменен только тип отражения.

Новые функциональные возможности в настоящее время реализованы только в среде выполнения CoreCLR и MetadataLoadContext.

Новые API были добавлены в System.Type, например IsFunctionPointer, и в System.Reflection.PropertyInfo, System.Reflection.FieldInfoи System.Reflection.ParameterInfo. В следующем коде показано, как использовать некоторые из новых API для отражения.

using System;
using System.Reflection;

// Sample class that contains a function pointer field.
public unsafe class UClass
{
    public delegate* unmanaged[Cdecl, SuppressGCTransition]<in int, void> _fp;
}

internal class FunctionPointerReflection
{
    public static void RunIt()
    {
        FieldInfo? fieldInfo = typeof(UClass).GetField(nameof(UClass._fp));

        // Obtain the function pointer type from a field.
        Type? fpType = fieldInfo?.FieldType;

        // New methods to determine if a type is a function pointer.
        Console.WriteLine(
        $"IsFunctionPointer: {fpType?.IsFunctionPointer}");
        Console.WriteLine(
            $"IsUnmanagedFunctionPointer: {fpType?.IsUnmanagedFunctionPointer}");

        // New methods to obtain the return and parameter types.
        Console.WriteLine($"Return type: {fpType?.GetFunctionPointerReturnType()}");

        if (fpType is not null)
        {
            foreach (Type parameterType in fpType.GetFunctionPointerParameterTypes())
            {
                Console.WriteLine($"Parameter type: {parameterType}");
            }
        }

        // Access to custom modifiers and calling conventions requires a "modified type".
        Type? modifiedType = fieldInfo?.GetModifiedFieldType();

        // A modified type forwards most members to its underlying type.
        Type? normalType = modifiedType?.UnderlyingSystemType;

        if (modifiedType is not null)
        {
            // New method to obtain the calling conventions.
            foreach (Type callConv in modifiedType.GetFunctionPointerCallingConventions())
            {
                Console.WriteLine($"Calling convention: {callConv}");
            }
        }

        // New method to obtain the custom modifiers.
        Type[]? modifiers =
            modifiedType?.GetFunctionPointerParameterTypes()[0].GetRequiredCustomModifiers();

        if (modifiers is not null)
        {
            foreach (Type modreq in modifiers)
            {
                Console.WriteLine($"Required modifier for first parameter: {modreq}");
            }
        }
    }
}

В предыдущем примере создаются следующие выходные данные:

IsFunctionPointer: True
IsUnmanagedFunctionPointer: True
Return type: System.Void
Parameter type: System.Int32&
Calling convention: System.Runtime.CompilerServices.CallConvSuppressGCTransition
Calling convention: System.Runtime.CompilerServices.CallConvCdecl
Required modifier for first parameter: System.Runtime.InteropServices.InAttribute

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

В .NET 8 было улучшено System.Text.Json множество улучшений для сериализации и десериализации. Например, можно настроить обработку элементов, которые не входят в полезные данные JSON.

В следующих разделах описаны другие улучшения сериализации:

Дополнительные сведения о сериализации JSON в целом см . в статье о сериализации и десериализации JSON в .NET.

Встроенная поддержка дополнительных типов

Сериализатор имеет встроенную поддержку следующих дополнительных типов.

  • Half, Int128и UInt128 числовые типы.

    Console.WriteLine(JsonSerializer.Serialize(
        [ Half.MaxValue, Int128.MaxValue, UInt128.MaxValue ]
    ));
    // [65500,170141183460469231731687303715884105727,340282366920938463463374607431768211455]
    
  • Memory<T> и ReadOnlyMemory<T> значения. byte значения сериализуются в строки Base64 и другие типы в массивы JSON.

    JsonSerializer.Serialize<ReadOnlyMemory<byte>>(new byte[] { 1, 2, 3 }); // "AQID"
    JsonSerializer.Serialize<Memory<int>>(new int[] { 1, 2, 3 }); // [1,2,3]
    

Генератор исходного кода

.NET 8 включает усовершенствования генератора источников System.Text.Json, предназначенные для создания собственного интерфейса AOT на уровне сериализатора на основе отражения. Например:

  • Теперь генератор источников поддерживает сериализацию типов с помощью required свойств и init свойств. Они уже поддерживаются в сериализации на основе отражения.

  • Улучшено форматирование исходного кода.

  • JsonSourceGenerationOptionsAttribute четность признаков с JsonSerializerOptions. Дополнительные сведения см. в разделе "Указание параметров (создание источника)".

  • Дополнительные диагностика (например, SYSLIB1034 и SYSLIB1039).

  • Не включать типы игнорируемых или недоступных свойств.

  • Поддержка вложенных JsonSerializerContext объявлений в произвольных типах.

  • Поддержка созданных компилятором или ненаписываемых типов в сценариях создания слабо типизированных источников. Так как созданные компилятором типы не могут быть явно указаны генератором источника, System.Text.Json теперь выполняет разрешение ближайшего предка во время выполнения. Это разрешение определяет наиболее подходящий супертип, с помощью которого сериализуется значение.

  • Новый тип JsonStringEnumConverter<TEnum>преобразователя. Существующий JsonStringEnumConverter класс не поддерживается в Native AOT. Вы можете запишите типы перечислений следующим образом:

    [JsonConverter(typeof(JsonStringEnumConverter<MyEnum>))]
    public enum MyEnum { Value1, Value2, Value3 }
    
    [JsonSerializable(typeof(MyEnum))]
    public partial class MyContext : JsonSerializerContext { }
    

    Дополнительные сведения см. в разделе "Сериализация полей перечисления в виде строк".

  • Новое JsonConverter.Type свойство позволяет искать тип не универсального JsonConverter экземпляра:

    Dictionary<Type, JsonConverter> CreateDictionary(IEnumerable<JsonConverter> converters)
        => converters.Where(converter => converter.Type != null)
                     .ToDictionary(converter => converter.Type!);
    

    Свойство имеет значение NULL, так как возвращается null для JsonConverterFactory экземпляров и typeof(T) для JsonConverter<T> экземпляров.

Генераторы источников цепочки

Класс JsonSerializerOptions включает новое TypeInfoResolverChain свойство, которое дополняет существующее TypeInfoResolver свойство. Эти свойства используются в настройке контракта для цепочки генераторов источников. Добавление нового свойства означает, что вам не нужно указывать все связанные компоненты на одном сайте вызова— их можно добавить после факта. TypeInfoResolverChain также позволяет интроспектирует цепочку или удаляет компоненты из него. Дополнительные сведения см. в разделе "Объединение генераторов источников".

Кроме того, JsonSerializerOptions.AddContext<TContext>() теперь устарело. Он заменен свойствами и TypeInfoResolverChain свойствамиTypeInfoResolver. Дополнительные сведения см. в SYSLIB0049.

Иерархии интерфейсов

.NET 8 добавляет поддержку сериализации свойств из иерархий интерфейса.

В следующем коде показан пример, в котором свойства как из немедленно реализованного интерфейса, так и его базового интерфейса сериализуются.

public static void InterfaceHierarchies()
{
    IDerived value = new DerivedImplement { Base = 0, Derived = 1 };
    string json = JsonSerializer.Serialize(value);
    Console.WriteLine(json); // {"Derived":1,"Base":0}
}

public interface IBase
{
    public int Base { get; set; }
}

public interface IDerived : IBase
{
    public int Derived { get; set; }
}

public class DerivedImplement : IDerived
{
    public int Base { get; set; }
    public int Derived { get; set; }
}

Политики именования

JsonNamingPolicy включает новые политики snake_case именования (с подчеркиванием) и kebab-case (с дефисом) преобразования имен свойств. Используйте эти политики аналогично существующей JsonNamingPolicy.CamelCase политике:

var options = new JsonSerializerOptions
{
    PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower
};
JsonSerializer.Serialize(new { PropertyName = "value" }, options);
// { "property_name" : "value" }

Дополнительные сведения см. в статье "Использование встроенной политики именования".

Свойства только для чтения

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

Чтобы принять участие в этой поддержке глобально, задайте новый параметр , PreferredObjectCreationHandlingдля JsonObjectCreationHandling.Populate. Если совместимость является проблемой, можно также включить функциональность более детально, поместив [JsonObjectCreationHandling(JsonObjectCreationHandling.Populate)] атрибут на определенные типы, свойства которых необходимо заполнить или по отдельным свойствам.

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

public static void ReadOnlyProperties()
{
    CustomerInfo customer = JsonSerializer.Deserialize<CustomerInfo>("""
        { "Names":["John Doe"], "Company":{"Name":"Contoso"} }
        """)!;

    Console.WriteLine(JsonSerializer.Serialize(customer));
}

class CompanyInfo
{
    public required string Name { get; set; }
    public string? PhoneNumber { get; set; }
}

[JsonObjectCreationHandling(JsonObjectCreationHandling.Populate)]
class CustomerInfo
{
    // Both of these properties are read-only.
    public List<string> Names { get; } = new();
    public CompanyInfo Company { get; } = new()
    {
        Name = "N/A",
        PhoneNumber = "N/A"
    };
}

До .NET 8 входные значения были проигнорированы, а NamesCompany свойства сохранили значения по умолчанию.

{"Names":[],"Company":{"Name":"N/A","PhoneNumber":"N/A"}}

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

{"Names":["John Doe"],"Company":{"Name":"Contoso","PhoneNumber":"N/A"}}

Дополнительные сведения о поведении десериализации заполнений см. в разделе "Заполнение инициализированных свойств".

Отключение по умолчанию на основе отражения

Теперь сериализатор на основе отражения можно отключить по умолчанию. Это отключение полезно, чтобы избежать случайного корня компонентов отражения, которые даже не используются, особенно в обрезках и собственных приложениях AOT. Чтобы отключить сериализацию на основе отражения по умолчанию, требуя JsonSerializerOptions передать JsonSerializer аргумент в методы сериализации и десериализации, задайте JsonSerializerIsReflectionEnabledByDefault для свойства MSBuild значение false в файле проекта.

Используйте новый IsReflectionEnabledByDefault API, чтобы проверка значение коммутатора функций. Если вы являетесь автором библиотеки, построенной на основе System.Text.Json, вы можете полагаться на свойство, чтобы настроить значения по умолчанию без случайного корневого отражения компонентов.

Дополнительные сведения см. в разделе "Отключение отражения по умолчанию".

Новые методы API JsonNode

System.Text.Json.Nodes.JsonArray К JsonNode ним относятся следующие новые методы.

public partial class JsonNode
{
    // Creates a deep clone of the current node and all its descendants.
    public JsonNode DeepClone();

    // Returns true if the two nodes are equivalent JSON representations.
    public static bool DeepEquals(JsonNode? node1, JsonNode? node2);

    // Determines the JsonValueKind of the current node.
    public JsonValueKind GetValueKind(JsonSerializerOptions options = null);

    // If node is the value of a property in the parent
    // object, returns its name.
    // Throws InvalidOperationException otherwise.
    public string GetPropertyName();

    // If node is the element of a parent JsonArray,
    // returns its index.
    // Throws InvalidOperationException otherwise.
    public int GetElementIndex();

    // Replaces this instance with a new value,
    // updating the parent object/array accordingly.
    public void ReplaceWith<T>(T value);

    // Asynchronously parses a stream as UTF-8 encoded data
    // representing a single JSON value into a JsonNode.
    public static Task<JsonNode?> ParseAsync(
        Stream utf8Json,
        JsonNodeOptions? nodeOptions = null,
        JsonDocumentOptions documentOptions = default,
        CancellationToken cancellationToken = default);
}

public partial class JsonArray
{
    // Returns an IEnumerable<T> view of the current array.
    public IEnumerable<T> GetValues<T>();
}

Недоступные члены

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

public static void NonPublicMembers()
{
    string json = JsonSerializer.Serialize(new MyPoco(42));
    Console.WriteLine(json);
    // {"X":42}

    JsonSerializer.Deserialize<MyPoco>(json);
}

public class MyPoco
{
    [JsonConstructor]
    internal MyPoco(int x) => X = x;

    [JsonInclude]
    internal int X { get; }
}

Дополнительные сведения см. в разделе "Неизменяемые типы" и неизменяемые члены и методы доступа.

API десериализации потоковой передачи

.NET 8 включает новые IAsyncEnumerable<T> методы расширения десериализации потоковой передачи, например GetFromJsonAsAsyncEnumerable. Аналогичные методы существовали, например, возвращаемыеTask<TResult>HttpClientJsonExtensions.GetFromJsonAsync. Новые методы расширения вызывают API потоковой передачи и возвращаются IAsyncEnumerable<T>.

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

public async static void StreamingDeserialization()
{
    const string RequestUri = "https://api.contoso.com/books";
    using var client = new HttpClient();
    IAsyncEnumerable<Book?> books = client.GetFromJsonAsAsyncEnumerable<Book>(RequestUri);

    await foreach (Book? book in books)
    {
        Console.WriteLine($"Read book '{book?.title}'");
    }
}

public record Book(int id, string title, string author, int publishedYear);

Метод расширения WithAddedModifier

Новый WithAddedModifier(IJsonTypeInfoResolver, Action<JsonTypeInfo>) метод расширения позволяет легко вносить изменения в контракты сериализации произвольных IJsonTypeInfoResolver экземпляров.

var options = new JsonSerializerOptions
{
    TypeInfoResolver = MyContext.Default
        .WithAddedModifier(static typeInfo =>
        {
            foreach (JsonPropertyInfo prop in typeInfo.Properties)
            {
                prop.Name = prop.Name.ToUpperInvariant();
            }
        })
};

Новые перегрузки JsonContent.Create

Теперь можно создавать JsonContent экземпляры с помощью контрактов, созданных с помощью тримобезопасных или исходных контрактов. Новые методы:

var book = new Book(id: 42, "Title", "Author", publishedYear: 2023);
HttpContent content = JsonContent.Create(book, MyContext.Default.Book);

public record Book(int id, string title, string author, int publishedYear);

[JsonSerializable(typeof(Book))]
public partial class MyContext : JsonSerializerContext
{
}

Замораживание экземпляра JsonSerializerOptions

Следующие новые методы позволяют контролировать, когда JsonSerializerOptions экземпляр заморожен:

  • JsonSerializerOptions.MakeReadOnly()

    Эта перегрузка предназначена для обеспечения безопасности обрезки и поэтому создает исключение в случаях, когда экземпляр параметров не настроен с помощью сопоставителя.

  • JsonSerializerOptions.MakeReadOnly(Boolean)

    Если вы передаете true эту перегрузку, он заполняет экземпляр параметров разрешением отражения по умолчанию, если отсутствует. Этот метод помечен RequiresUnreferenceCode/RequiresDynamicCode и поэтому непригоден для приложений Native AOT.

Новое IsReadOnly свойство позволяет проверка, если экземпляр параметров заморожен.

Абстракция времени

Новый TimeProvider класс и ITimer интерфейс добавляют функции абстракции времени, что позволяет макетировать время в сценариях тестирования. Кроме того, можно использовать абстракции времени для имитации Task операций, которые зависят от хода выполнения времени с помощью Task.Delay и Task.WaitAsync. Абстракция времени поддерживает следующие основные операции времени:

  • Получение локального и UTC-времени
  • Получение метки времени для измерения производительности
  • Создание таймера

В следующем фрагменте кода показаны некоторые примеры использования.

// Get system time.
DateTimeOffset utcNow = TimeProvider.System.GetUtcNow();
DateTimeOffset localNow = TimeProvider.System.GetLocalNow();

TimerCallback callback = s => ((State)s!).Signal();

// Create a timer using the time provider.
ITimer timer = _timeProvider.CreateTimer(
    callback, null, TimeSpan.Zero, Timeout.InfiniteTimeSpan);

// Measure a period using the system time provider.
long providerTimestamp1 = TimeProvider.System.GetTimestamp();
long providerTimestamp2 = TimeProvider.System.GetTimestamp();

TimeSpan period = _timeProvider.GetElapsedTime(providerTimestamp1, providerTimestamp2);
// Create a time provider that works with a
// time zone that's different than the local time zone.
private class ZonedTimeProvider(TimeZoneInfo zoneInfo) : TimeProvider()
{
    private readonly TimeZoneInfo _zoneInfo = zoneInfo ?? TimeZoneInfo.Local;

    public override TimeZoneInfo LocalTimeZone => _zoneInfo;

    public static TimeProvider FromLocalTimeZone(TimeZoneInfo zoneInfo) =>
        new ZonedTimeProvider(zoneInfo);
}

Улучшения UTF8

Если вы хотите включить запись строкового представления типа в целевой диапазон, реализуйте новый IUtf8SpanFormattable интерфейс в типе. Этот новый интерфейс тесно связан с ISpanFormattable, но предназначен для UTF8 и Span<byte> вместо UTF16 и Span<char>.

IUtf8SpanFormattableреализован для всех примитивных типов (а также других) с той же общей логикой, независимо от целевой stringSpan<char>илиSpan<byte>. Она имеет полную поддержку всех форматов (включая новый описатель двоичного кода B) и все региональные параметры. Это означает, что теперь можно форматировать непосредственно в UTF8 из Byte, DateOnlyComplexCharDateTimeDateTimeOffsetDecimalDoubleGuidHalfIPAddressIPNetworkInt16Int32Int64Int128IntPtrNFloatSByteSingleRuneTimeOnlyTimeSpanUInt16UInt32UInt64UInt128UIntPtrVersion

Новые Utf8.TryWrite методы предоставляют аналог на основе UTF8 существующим MemoryExtensions.TryWrite методам, которые основаны на UTF16. Можно использовать интерполированный синтаксис строки для форматирования сложного выражения непосредственно в диапазоне байтов UTF8, например:

static bool FormatHexVersion(
    short major,
    short minor,
    short build,
    short revision,
    Span<byte> utf8Bytes,
    out int bytesWritten) =>
    Utf8.TryWrite(
        utf8Bytes,
        CultureInfo.InvariantCulture,
        $"{major:X4}.{minor:X4}.{build:X4}.{revision:X4}",
        out bytesWritten);

Реализация распознает IUtf8SpanFormattable значения формата и использует их реализации для записи их представлений UTF8 непосредственно в целевой диапазон.

Реализация также использует новый Encoding.TryGetBytes(ReadOnlySpan<Char>, Span<Byte>, Int32) метод, который вместе с его Encoding.TryGetChars(ReadOnlySpan<Byte>, Span<Char>, Int32) коллегой поддерживает кодировку и декодирование в целевом диапазоне. Если диапазон не достаточно длинный для хранения результирующего состояния, методы возвращаются false , а не вызывают исключение.

Методы для работы с случайностью

В System.Random этих и System.Security.Cryptography.RandomNumberGenerator типах представлены два новых метода для работы с случайностью.

GetItems<T>()

Новые System.Random.GetItems и System.Security.Cryptography.RandomNumberGenerator.GetItems методы позволяют случайным образом выбрать указанное количество элементов из входного набора. В следующем примере показано, как использовать System.Random.GetItems<T>() (на экземпляре, предоставленном Random.Shared свойством), чтобы случайно вставлять 31 элементы в массив. Этот пример можно использовать в игре Simon, где игроки должны помнить последовательность цветных кнопок.

private static ReadOnlySpan<Button> s_allButtons = new[]
{
    Button.Red,
    Button.Green,
    Button.Blue,
    Button.Yellow,
};

// ...

Button[] thisRound = Random.Shared.GetItems(s_allButtons, 31);
// Rest of game goes here ...

Перетасовка<T>()

Новые Random.Shuffle и RandomNumberGenerator.Shuffle<T>(Span<T>) методы позволяют случайным образом упорядочивать диапазон. Эти методы полезны для уменьшения предвзятости обучения в машинном обучении (поэтому первое, что не всегда обучение, и последнее, что всегда тестировать).

YourType[] trainingData = LoadTrainingData();
Random.Shared.Shuffle(trainingData);

IDataView sourceData = mlContext.Data.LoadFromEnumerable(trainingData);

DataOperationsCatalog.TrainTestData split = mlContext.Data.TrainTestSplit(sourceData);
model = chain.Fit(split.TrainSet);

IDataView predictions = model.Transform(split.TestSet);
// ...

Типы, ориентированные на производительность

.NET 8 представляет несколько новых типов, направленных на повышение производительности приложения.

  • Новое System.Collections.Frozen пространство имен включает типы FrozenDictionary<TKey,TValue> коллекций и FrozenSet<T>. Эти типы не позволяют вносить изменения в ключи и значения после создания коллекции. Это требование позволяет ускорить операции чтения (например, TryGetValue()). Эти типы особенно полезны для коллекций, которые заполняются при первом использовании, а затем сохраняются в течение длительной службы, например:

    private static readonly FrozenDictionary<string, bool> s_configurationData =
        LoadConfigurationData().ToFrozenDictionary(optimizeForReads: true);
    
    // ...
    if (s_configurationData.TryGetValue(key, out bool setting) && setting)
    {
        Process();
    }
    
  • Методы, такие как MemoryExtensions.IndexOfAny поиск первого вхождения любого значения в переданной коллекции. Новый System.Buffers.SearchValues<T> тип предназначен для передаче в такие методы. Соответственно, .NET 8 добавляет новые перегрузки методов, таких как MemoryExtensions.IndexOfAny принятие экземпляра нового типа. При создании экземпляра все данные, необходимые для оптимизации последующих поисковых SearchValues<T>запросов, являются производными в то время, что означает, что работа выполняется заранее.

  • Новый System.Text.CompositeFormat тип полезен для оптимизации строк форматирования, которые не известны во время компиляции (например, если строка форматирования загружается из файла ресурса). Немного дополнительного времени тратится перед выполнением таких действий, как анализ строки, но он экономит работу от каждого использования.

    private static readonly CompositeFormat s_rangeMessage =
        CompositeFormat.Parse(LoadRangeMessageResource());
    
    // ...
    static string GetMessage(int min, int max) =>
        string.Format(CultureInfo.InvariantCulture, s_rangeMessage, min, max);
    
  • Новые System.IO.Hashing.XxHash3 и System.IO.Hashing.XxHash128 типы обеспечивают реализацию быстрых алгоритмов хэширования XXH3 и XXH128.

System.Numerics и System.Runtime.Intrinsics

В этом разделе рассматриваются улучшения System.Numerics пространств имен и System.Runtime.Intrinsics пространств имен.

  • Vector256<T>, Matrix3x2и Matrix4x4 улучшили аппаратное ускорение в .NET 8. Например, Vector256<T> было повторно выполнено внутреннее 2x Vector128<T> использование операций, где это возможно. Это позволяет частичное ускорение некоторых функций, Vector128.IsHardwareAccelerated == trueVector256.IsHardwareAccelerated == falseно, например, в Arm64.
  • Встроенные компоненты оборудования теперь аннотированы атрибутом ConstExpected . Это гарантирует, что пользователи знают, когда базовое оборудование ожидает константы и поэтому, когда неконстантное значение может неожиданно повредить производительность.
  • API Lerp(TSelf, TSelf, TSelf)Lerp был добавлен и поэтому float в IFloatingPointIeee754<TSelf> (Single), double (Double) и Half. Этот API позволяет эффективно и правильно выполнять линейную интерполяцию между двумя значениями.

Vector512 и AVX-512

Расширенная поддержка SIMD для .NET Core 3.0 для включения встроенных API оборудования для x86/x64 для конкретной платформы. Добавлена поддержка .NET 5 для Arm64 и .NET 7, добавлена кроссплатформенная встроенная архитектура оборудования. .NET 8 поддерживает SIMD путем внедрения Vector512<T> и поддержки расширенных векторных расширений Intel 512 (AVX-512) инструкций.

В частности, .NET 8 включает поддержку следующих ключевых функций AVX-512:

  • 512-разрядные операции векторов
  • Дополнительные 16 регистров SIMD
  • Дополнительные инструкции, доступные для 128-разрядных, 256-разрядных и 512-разрядных векторов

Если у вас есть оборудование, поддерживающее функциональные возможности, теперь Vector512.IsHardwareAccelerated сообщается true.

.NET 8 также добавляет несколько классов для конкретной платформы в System.Runtime.Intrinsics.X86 пространстве имен:

  • Avx512F (базовый)
  • Avx512BW (байт и слово)
  • Avx512CD (обнаружение конфликтов)
  • Avx512DQ (doubleword и quadword)
  • Avx512Vbmi (инструкции по манипуляции векторными байтами)

Эти классы соответствуют той же общей форме, что и другие архитектуры наборов инструкций (ISAs), в том, что они предоставляют IsSupported свойство и вложенный Avx512F.X64 класс для инструкций, доступных только для 64-разрядных процессов. Кроме того, каждый класс имеет вложенный Avx512F.VL класс, предоставляющий Avx512VL расширения (векторная длина) для соответствующего набора инструкций.

Даже если в коде явно не используются Vector512специальные или Avx512Fконкретные инструкции, скорее всего, вы по-прежнему будете воспользоваться новой поддержкой AVX-512. JIT может использовать дополнительные регистры и инструкции неявно при использовании Vector128<T> или Vector256<T>. Библиотека базовых классов использует эти встроенные компоненты оборудования внутри большинства операций, предоставляемых Span<T> и ReadOnlySpan<T> во многих математических API, предоставляемых для примитивных типов.

Проверка данных

Пространство System.ComponentModel.DataAnnotations имен включает новые атрибуты проверки данных, предназначенные для сценариев проверки в облачных службах. Хотя предварительно существующие DataAnnotations проверяющие элементы ориентированы на обычную проверку ввода данных пользовательского интерфейса, например поля в форме, новые атрибуты предназначены для проверки данных, не являющихся пользователями, таких как параметры конфигурации. В дополнение к новым атрибутам новые свойства были добавлены в RangeAttribute и RequiredAttribute типы.

Новый API Description
RangeAttribute.MinimumIsExclusive
RangeAttribute.MaximumIsExclusive
Указывает, включены ли границы в допустимый диапазон.
System.ComponentModel.DataAnnotations.LengthAttribute Задает как нижние, так и верхние границы для строк или коллекций. Например, [Length(10, 20)] требуется не менее 10 элементов и не более 20 элементов в коллекции.
System.ComponentModel.DataAnnotations.Base64StringAttribute Проверяет, является ли строка допустимым представлением Base64.
System.ComponentModel.DataAnnotations.AllowedValuesAttribute
System.ComponentModel.DataAnnotations.DeniedValuesAttribute
Укажите списки разрешений и списки запретов соответственно. Например, [AllowedValues("apple", "banana", "mango")].

Метрики

Новые API позволяют прикрепить теги пар "ключ-значение" к Meter объектам при Instrument их создании. Агрегаторы опубликованных измерений метрик могут использовать теги для отличия агрегированных значений.

var options = new MeterOptions("name")
{
    Version = "version",
    // Attach these tags to the created meter.
    Tags = new TagList()
    {
        { "MeterKey1", "MeterValue1" },
        { "MeterKey2", "MeterValue2" }
    }
};

Meter meter = meterFactory!.Create(options);

Counter<int> counterInstrument = meter.CreateCounter<int>(
    "counter", null, null, new TagList() { { "counterKey1", "counterValue1" } }
);
counterInstrument.Add(1);

Новые API включают следующее:

Шифрование

.NET 8 добавляет поддержку примитивов хэширования SHA-3. (SHA-3 в настоящее время поддерживается Linux с OpenSSL 1.1.1 или более поздней версии и Windows 11 сборки 25324 или более поздней версии.) API- интерфейсы, где SHA-2 теперь доступны, предлагают комплимент SHA-3. Это включает SHA3_256, SHA3_384а также хэширование; HMACSHA3_384HMACSHA3_256, а также SHA3_512HMACSHA3_512 для HMAC; HashAlgorithmName.SHA3_256, HashAlgorithmName.SHA3_384а также HashAlgorithmName.SHA3_512 для хэширования, где алгоритм настраивается; и , RSAEncryptionPadding.OaepSHA3_384а RSAEncryptionPadding.OaepSHA3_256RSAEncryptionPadding.OaepSHA3_512 также для шифрования RSA OAEP.

В следующем примере показано, как использовать API, включая SHA3_256.IsSupported свойство, чтобы определить, поддерживает ли платформа SHA-3.

// Hashing example
if (SHA3_256.IsSupported)
{
    byte[] hash = SHA3_256.HashData(dataToHash);
}
else
{
    // ...
}

// Signing example
if (SHA3_256.IsSupported)
{
     using ECDsa ec = ECDsa.Create(ECCurve.NamedCurves.nistP256);
     byte[] signature = ec.SignData(dataToBeSigned, HashAlgorithmName.SHA3_256);
}
else
{
    // ...
}

Поддержка SHA-3 в настоящее время направлена на поддержку криптографических примитивов. Более высокий уровень конструкций и протоколов, как ожидается, не будет полностью поддерживать SHA-3 изначально. К этим протоколам относятся сертификаты SignedXmlX.509 и COSE.

Сеть

Поддержка прокси-сервера HTTPS

До сих пор типы прокси,которые HttpClient поддерживали все разрешенные "man-in-the-middle", чтобы увидеть, к какой сайту подключается клиент, даже для URI HTTPS. HttpClient теперь поддерживает прокси-сервер HTTPS, который создает зашифрованный канал между клиентом и прокси-сервером, чтобы все запросы могли обрабатываться с полной конфиденциальностью.

Чтобы включить прокси-сервер HTTPS, задайте all_proxy переменную среды или используйте WebProxy класс для программного управления прокси-сервером.

Unix: export all_proxy=https://x.x.x.x:3218 Windows: set all_proxy=https://x.x.x.x:3218

Можно также использовать WebProxy класс для программного управления прокси-сервером.

Методы ZipFile на основе потока

.NET 8 включает новые перегрузки ZipFile.CreateFromDirectory , которые позволяют собирать все файлы, включенные в каталог, и zip их, а затем хранить полученный ZIP-файл в предоставленный поток. Аналогичным образом новые ZipFile.ExtractToDirectory перегрузки позволяют предоставить поток, содержащий zippped-файл, и извлечь его содержимое в файловую систему. Это новые перегрузки:

namespace System.IO.Compression;

public static partial class ZipFile
{
    public static void CreateFromDirectory(
        string sourceDirectoryName, Stream destination);

    public static void CreateFromDirectory(
        string sourceDirectoryName,
        Stream destination,
        CompressionLevel compressionLevel,
        bool includeBaseDirectory);

    public static void CreateFromDirectory(
        string sourceDirectoryName,
        Stream destination,
        CompressionLevel compressionLevel,
        bool includeBaseDirectory,
    Encoding? entryNameEncoding);

    public static void ExtractToDirectory(
        Stream source, string destinationDirectoryName) { }

    public static void ExtractToDirectory(
        Stream source, string destinationDirectoryName, bool overwriteFiles) { }

    public static void ExtractToDirectory(
        Stream source, string destinationDirectoryName, Encoding? entryNameEncoding) { }

    public static void ExtractToDirectory(
        Stream source, string destinationDirectoryName, Encoding? entryNameEncoding, bool overwriteFiles) { }
}

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

Библиотеки расширений

В этом разделе содержатся следующие подтопики:

Ключевые службы DI

Службы внедрения зависимостей ключа (DI) предоставляют средства для регистрации и получения служб DI с помощью ключей. Используя ключи, вы можете область способ регистрации и использования служб. Это некоторые из новых API:

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

WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
builder.Services.AddSingleton<BigCacheConsumer>();
builder.Services.AddSingleton<SmallCacheConsumer>();
builder.Services.AddKeyedSingleton<ICache, BigCache>("big");
builder.Services.AddKeyedSingleton<ICache, SmallCache>("small");
WebApplication app = builder.Build();
app.MapGet("/big", (BigCacheConsumer data) => data.GetData());
app.MapGet("/small", (SmallCacheConsumer data) => data.GetData());
app.MapGet("/big-cache", ([FromKeyedServices("big")] ICache cache) => cache.Get("data"));
app.MapGet("/small-cache", (HttpContext httpContext) => httpContext.RequestServices.GetRequiredKeyedService<ICache>("small").Get("data"));
app.Run();

class BigCacheConsumer([FromKeyedServices("big")] ICache cache)
{
    public object? GetData() => cache.Get("data");
}

class SmallCacheConsumer(IServiceProvider serviceProvider)
{
    public object? GetData() => serviceProvider.GetRequiredKeyedService<ICache>("small").Get("data");
}

public interface ICache
{
    object Get(string key);
}

public class BigCache : ICache
{
    public object Get(string key) => $"Resolving {key} from big cache.";
}

public class SmallCache : ICache
{
    public object Get(string key) => $"Resolving {key} from small cache.";
}

Дополнительные сведения см. в статье dotnet/runtime#64427.

Размещенные службы жизненного цикла

Размещенные службы теперь имеют больше возможностей для выполнения во время жизненного цикла приложения. IHostedServiceStartAsync предоставлены и StopAsyncтеперь IHostedLifecycleService предоставляются следующие дополнительные методы:

Эти методы выполняются до и после существующих точек соответственно.

В следующем примере показано, как использовать новые API.

using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

internal class HostedLifecycleServices
{
    public async static void RunIt()
    {
        IHostBuilder hostBuilder = new HostBuilder();
        hostBuilder.ConfigureServices(services =>
        {
            services.AddHostedService<MyService>();
        });

        using (IHost host = hostBuilder.Build())
        {
            await host.StartAsync();
        }
    }

    public class MyService : IHostedLifecycleService
    {
        public Task StartingAsync(CancellationToken cancellationToken) => /* add logic here */ Task.CompletedTask;
        public Task StartAsync(CancellationToken cancellationToken) => /* add logic here */ Task.CompletedTask;
        public Task StartedAsync(CancellationToken cancellationToken) => /* add logic here */ Task.CompletedTask;
        public Task StopAsync(CancellationToken cancellationToken) => /* add logic here */ Task.CompletedTask;
        public Task StoppedAsync(CancellationToken cancellationToken) => /* add logic here */ Task.CompletedTask;
        public Task StoppingAsync(CancellationToken cancellationToken) => /* add logic here */ Task.CompletedTask;
    }
}

Дополнительные сведения см. в статье dotnet/runtime#86511.

Проверка параметров

Генератор исходного кода

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

public class FirstModelNoNamespace
{
    [Required]
    [MinLength(5)]
    public string P1 { get; set; } = string.Empty;

    [Microsoft.Extensions.Options.ValidateObjectMembers(
        typeof(SecondValidatorNoNamespace))]
    public SecondModelNoNamespace? P2 { get; set; }
}

public class SecondModelNoNamespace
{
    [Required]
    [MinLength(5)]
    public string P4 { get; set; } = string.Empty;
}

[OptionsValidator]
public partial class FirstValidatorNoNamespace
    : IValidateOptions<FirstModelNoNamespace>
{
}

[OptionsValidator]
public partial class SecondValidatorNoNamespace
    : IValidateOptions<SecondModelNoNamespace>
{
}

Если приложение использует внедрение зависимостей, можно внедрить проверку, как показано в следующем примере кода.

WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllersWithViews();
builder.Services.Configure<FirstModelNoNamespace>(
    builder.Configuration.GetSection("some string"));

builder.Services.AddSingleton<
    IValidateOptions<FirstModelNoNamespace>, FirstValidatorNoNamespace>();
builder.Services.AddSingleton<
    IValidateOptions<SecondModelNoNamespace>, SecondValidatorNoNamespace>();

Тип ValidateOptionsResultBuilder

.NET 8 представляет ValidateOptionsResultBuilder тип для упрощения создания ValidateOptionsResult объекта. Важно, что этот построитель позволяет накапливать несколько ошибок. Ранее создание объекта, необходимого ValidateOptionsResult для реализации IValidateOptions<TOptions>.Validate(String, TOptions) , было сложным и иногда приводило к ошибкам многоуровневой проверки. При наличии нескольких ошибок процесс проверки часто останавливается при первой ошибке.

В следующем фрагменте кода показан пример использования ValidateOptionsResultBuilder.

ValidateOptionsResultBuilder builder = new();
builder.AddError("Error: invalid operation code");
builder.AddResult(ValidateOptionsResult.Fail("Invalid request parameters"));
builder.AddError("Malformed link", "Url");

// Build ValidateOptionsResult object has accumulating multiple errors.
ValidateOptionsResult result = builder.Build();

// Reset the builder to allow using it in new validation operation.
builder.Clear();

Конструкторы LoggerMessageAttribute

LoggerMessageAttribute Теперь предлагает дополнительные перегрузки конструктора. Ранее необходимо выбрать конструктор без параметров или конструктор, который требовал все параметры (идентификатор события, уровень журнала и сообщение). Новые перегрузки обеспечивают большую гибкость при указании необходимых параметров с уменьшенным кодом. Если вы не предоставляете идентификатор события, система автоматически создает его.

public LoggerMessageAttribute(LogLevel level, string message);
public LoggerMessageAttribute(LogLevel level);
public LoggerMessageAttribute(string message);

Метрики расширений

Интерфейс IMeterFactory

Вы можете зарегистрировать новый IMeterFactory интерфейс в контейнерах внедрения зависимостей (DI) и использовать его для создания Meter объектов в изолированном режиме.

Зарегистрируйте контейнер IMeterFactory DI с помощью реализации фабрики меток по умолчанию:

// 'services' is the DI IServiceCollection.
services.AddMetrics();

Затем потребители могут получить фабрику счетчиков и использовать ее для создания нового Meter объекта.

IMeterFactory meterFactory = serviceProvider.GetRequiredService<IMeterFactory>();

MeterOptions options = new MeterOptions("MeterName")
{
    Version = "version",
};

Meter meter = meterFactory.Create(options);

Класс T> MetricCollector<

Новый MetricCollector<T> класс позволяет записывать измерения метрик вместе с метками времени. Кроме того, класс предлагает гибкость использования поставщика времени для точного создания метки времени.

const string CounterName = "MyCounter";
DateTimeOffset now = DateTimeOffset.Now;

var timeProvider = new FakeTimeProvider(now);
using var meter = new Meter(Guid.NewGuid().ToString());
Counter<long> counter = meter.CreateCounter<long>(CounterName);
using var collector = new MetricCollector<long>(counter, timeProvider);

Assert.IsNull(collector.LastMeasurement);

counter.Add(3);

// Verify the update was recorded.
Assert.AreEqual(counter, collector.Instrument);
Assert.IsNotNull(collector.LastMeasurement);

Assert.AreSame(collector.GetMeasurementSnapshot().Last(), collector.LastMeasurement);
Assert.AreEqual(3, collector.LastMeasurement.Value);
Assert.AreEqual(now, collector.LastMeasurement.Timestamp);

System.Numerics.Tensors.TensorPrimitives

Обновленный пакет NuGet System.Numerics.Tensors включает API в новое TensorPrimitives пространство имен, которое добавляет поддержку операций tensor. Тензорные примитивы оптимизируют рабочие нагрузки с большим объемом данных, такие как ИИ и машинное обучение.

Рабочие нагрузки искусственного интеллекта, такие как семантический поиск и расширенное создание (RAG), расширяют возможности естественного языка больших языковых моделей, таких как ChatGPT, расширяя запросы с соответствующими данными. Для этих рабочих нагрузок операции с векторами( например , сходство косинуса , чтобы найти наиболее релевантные данные для ответа на вопрос), имеют решающее значение. Пакет System.Numerics.Tensors.TensorPrimitives предоставляет API для операций векторов, что означает, что вам не нужно принимать внешнюю зависимость или писать собственную реализацию.

Этот пакет заменяет пакет System.Numerics.Tensors.

Дополнительные сведения см. в записи блога .NET 8 RC 2.

См. также