Partilhar via


O que há de novo no tempo de execução do .NET 8

Este artigo descreve novos recursos no tempo de execução do .NET para .NET 8.

Melhoramentos de desempenho

O .NET 8 inclui melhorias na geração de código e compilação just-in time (JIT):

  • Melhorias no desempenho do Arm64
  • Melhorias no SIMD
  • Suporte para extensões AVX-512 ISA (consulte Vetor512 e AVX-512)
  • Melhorias nativas da nuvem
  • Melhorias na taxa de transferência JIT
  • Loop e otimizações gerais
  • Acesso otimizado para campos marcados com ThreadStaticAttribute
  • Atribuição consecutiva de registos. Arm64 tem duas instruções para pesquisa de vetores de tabela, que exigem que todas as entidades em seus operandos de tupla estejam presentes em registros consecutivos.
  • JIT/NativeAOT agora pode desenrolar e vetorizar automaticamente algumas operações de memória com SIMD, como comparação, cópia e zero, se puder determinar seus tamanhos em tempo de compilação.

Além disso, a otimização orientada por perfil dinâmico (PGO) foi melhorada e agora está habilitada por padrão. Você não precisa mais usar uma opção de configuração de tempo de execução para habilitá-la. O PGO dinâmico trabalha lado a lado com a compilação hierárquica para otimizar ainda mais o código com base na instrumentação adicional implementada durante a camada 0.

Em média, o PGO dinâmico aumenta o desempenho em cerca de 15%. Em um conjunto de benchmark de ~4600 testes, 23% viram melhorias de desempenho de 20% ou mais.

Codegen struct promoção

O .NET 8 inclui um novo passo de otimização de promoção física para codegen que generaliza a capacidade do JIT de promover variáveis struct. Esta otimização (também chamada de substituição escalar de agregados) substitui os campos de variáveis struct por variáveis primitivas sobre as quais o JIT é então capaz de raciocinar e otimizar com mais precisão.

O JIT já suportava esta otimização, mas com várias grandes limitações, incluindo:

  • Só foi suportado para estruturas com quatro ou menos campos.
  • Ele só era suportado se cada campo fosse um tipo primitivo, ou uma estrutura simples envolvendo um tipo primitivo.

A promoção física remove essas limitações, o que corrige uma série de problemas JIT de longa data.

Recolha de lixo

O .NET 8 adiciona um recurso para ajustar o limite de memória imediatamente. Isso é útil em cenários de serviço em nuvem, onde a demanda vai e vem. Para serem rentáveis, os serviços devem aumentar e reduzir o consumo de recursos à medida que a procura flutua. Quando um serviço deteta uma diminuição na demanda, ele pode reduzir o consumo de recursos reduzindo seu limite de memória. Anteriormente, isso falharia porque o coletor de lixo (GC) não estava ciente da alteração e poderia alocar mais memória do que o novo limite. Com essa alteração, você pode chamar a RefreshMemoryLimit() API para atualizar o GC com o novo limite de memória.

Existem algumas limitações a ter em conta:

  • Em plataformas de 32 bits (por exemplo, Windows x86 e Linux ARM), o .NET não consegue estabelecer um novo limite rígido de heap se ainda não houver um.
  • A API pode retornar um código de status diferente de zero indicando que a atualização falhou. Isso pode acontecer se o scale-down for muito agressivo e não deixar espaço para o GC manobrar. Nesse caso, considere chamar GC.Collect(2, GCCollectionMode.Aggressive) para reduzir o uso de memória atual e tente novamente.
  • Se você aumentar o limite de memória além do tamanho que o GC acredita que o processo pode lidar durante a inicialização, a RefreshMemoryLimit chamada será bem-sucedida, mas não será capaz de usar mais memória do que o que ele percebe como o limite.

O trecho de código a seguir mostra como chamar a API.

GC.RefreshMemoryLimit();

Você também pode atualizar algumas das definições de configuração do GC relacionadas ao limite de memória. O trecho de código a seguir define o limite rígido de pilha para 100 mebibytes (MiB):

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

A API pode lançar um InvalidOperationException se o limite rígido for inválido, por exemplo, no caso de porcentagens negativas de limite rígido de heap e se o limite rígido for muito baixo. Isso pode acontecer se o limite rígido de heap que a atualização definirá, devido às novas configurações do AppData ou implícito pelas alterações no limite de memória do contêiner, for menor do que o que já foi confirmado.

Globalização para aplicativos móveis

Os aplicativos móveis no iOS, tvOS e MacCatalyst podem optar por um novo modo de globalização híbrida que usa um pacote de UTI mais leve. No modo híbrido, os dados de globalização são parcialmente extraídos do pacote de UTI e parcialmente de chamadas para APIs nativas. O modo híbrido serve todas as localidades suportadas pelo dispositivo móvel.

O modo híbrido é mais adequado para aplicativos que não podem funcionar no modo de globalização invariante e que usam culturas que foram cortadas dos dados da UTI em dispositivos móveis. Você também pode usá-lo quando quiser carregar um arquivo de dados de UTI menor. (O arquivo icudt_hybrid.dat é 34,5% menor do que o arquivo de dados padrão da UTI icudt.dat.)

Para usar o modo de globalização híbrida, defina a HybridGlobalization propriedade MSBuild como true:

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

Existem algumas limitações a ter em conta:

  • Devido às limitações da API nativa, nem todas as APIs de globalização são suportadas no modo híbrido.
  • Algumas das APIs suportadas têm comportamento diferente.

Para verificar se seu aplicativo é afetado, consulte Diferenças comportamentais.

Interoperabilidade COM gerada pela fonte

O .NET 8 inclui um novo gerador de código-fonte que suporta a interoperação com interfaces COM. Você pode usar o GeneratedComInterfaceAttribute para marcar uma interface como uma interface COM para o gerador de origem. O gerador de código-fonte gerará código para habilitar a chamada do código C# para o código não gerenciado. Ele também gera código para habilitar a chamada de código não gerenciado para C#. Este gerador de origem integra-se com LibraryImportAttributeo , e você pode usar tipos com os GeneratedComInterfaceAttribute parâmetros as e tipos de retorno em LibraryImportmétodos -atribuídos.

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);
}

O gerador de código-fonte também suporta o novo GeneratedComClassAttribute atributo para permitir que você passe tipos que implementam interfaces com o atributo para código GeneratedComInterfaceAttribute não gerenciado. O gerador de código-fonte gerará o código necessário para expor um objeto COM que implementa as interfaces e encaminha chamadas para a implementação gerenciada.

Os métodos em interfaces com o GeneratedComInterfaceAttribute atributo suportam todos os mesmos tipos que LibraryImportAttribute, e LibraryImportAttribute agora suportam GeneratedComInterfacetipos -atribuídos e GeneratedComClass-tipos atribuídos.

Se seu código C# usa apenas uma GeneratedComInterfaceinterface -atribuída para encapsular um objeto COM de código não gerenciado ou encapsular um objeto gerenciado de C# para expor a código não gerenciado, você pode usar as opções na Options propriedade para personalizar qual código será gerado. Essas opções significam que você não precisa escrever marshallers para cenários que você sabe que não serão usados.

O gerador de código-fonte usa o novo StrategyBasedComWrappers tipo para criar e gerenciar os wrappers de objeto COM e os wrappers de objeto gerenciado. Esse novo tipo fornece a experiência de usuário .NET esperada para interoperabilidade COM, enquanto fornece pontos de personalização para usuários avançados. Se seu aplicativo tiver seu próprio mecanismo para definir tipos de COM ou se você precisar oferecer suporte a cenários que o COM gerado pela fonte não oferece suporte atualmente, considere usar o novo StrategyBasedComWrappers tipo para adicionar os recursos ausentes para seu cenário e obter a mesma experiência de usuário do .NET para seus tipos COM.

Se você estiver usando o Visual Studio, novos analisadores e correções de código facilitam a conversão do código de interoperabilidade COM existente para usar a interoperabilidade gerada pelo código-fonte. Ao lado de cada interface que tem o ComImportAttribute, uma lâmpada oferece uma opção para converter em interoperabilidade gerada na fonte. A correção altera a interface para usar o GeneratedComInterfaceAttribute atributo. E ao lado de cada classe que implementa uma interface com GeneratedComInterfaceAttributeo , uma lâmpada oferece uma opção para adicionar o GeneratedComClassAttribute atributo ao tipo. Depois que seus tipos são convertidos, você pode mover seus DllImport métodos para usar LibraryImportAttribute.

Limitações

O gerador de origem COM não suporta afinidade de apartamento, usando a new palavra-chave para ativar um COM CoClass e as seguintes APIs:

  • IDispatch-interfaces baseadas em interfaces.
  • IInspectable-interfaces baseadas em interfaces.
  • Propriedades e eventos COM.

Gerador de fonte de vinculação de configuração

O .NET 8 introduz um gerador de código-fonte para fornecer AOT e configuração de corte amigável no ASP.NET Core. O gerador é uma alternativa à implementação baseada em reflexão pré-existente.

O gerador de origem investiga , Configure(TOptions)Binde Get chama para recuperar informações de tipo. Quando o gerador é habilitado em um projeto, o compilador escolhe implicitamente os métodos gerados em vez das implementações de estrutura baseadas em reflexão pré-existentes.

Nenhuma alteração no código-fonte é necessária para usar o gerador. Ele é ativado por padrão em aplicativos Web AOT'd. Para outros tipos de projeto, o gerador de código-fonte está desativado por padrão, mas você pode optar por participar definindo a EnableConfigurationBindingGenerator propriedade como true em seu arquivo de projeto:

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

O código a seguir mostra um exemplo de invocar o fichário.

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; }
    }
}

Principais bibliotecas .NET

Esta seção contém os seguintes subtópicos:

Reflexão

Ponteiros de função foram introduzidos no .NET 5, no entanto, o suporte correspondente para reflexão não foi adicionado naquele momento. Ao usar typeof ou refletir sobre um ponteiro de função, por exemplo, typeof(delegate*<void>()) ou FieldInfo.FieldType respectivamente, um IntPtr foi retornado. A partir do .NET 8, um System.Type objeto é retornado. Esse tipo fornece acesso aos metadados do ponteiro da função, incluindo as convenções de chamada, o tipo de retorno e os parâmetros.

Nota

Uma instância de ponteiro de função, que é um endereço físico para uma função, continua a ser representada como um IntPtrarquivo . Apenas o tipo de reflexão mudou.

Atualmente, a nova funcionalidade é implementada apenas no tempo de execução CoreCLR e MetadataLoadContext.

Novas APIs foram adicionadas ao System.Type, como IsFunctionPointer, e a System.Reflection.PropertyInfo, System.Reflection.FieldInfoe System.Reflection.ParameterInfo. O código a seguir mostra como usar algumas das novas APIs para reflexão.

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}");
            }
        }
    }
}

O exemplo anterior produz a seguinte saída:

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

Serialização

Muitas melhorias foram feitas na System.Text.Json funcionalidade de serialização e desserialização no .NET 8. Por exemplo, você pode personalizar a manipulação de membros que não estão na carga JSON útil.

As seções a seguir descrevem outros aprimoramentos de serialização:

Para obter mais informações sobre a serialização JSON em geral, consulte Serialização e desserialização JSON no .NET.

Suporte integrado para tipos adicionais

O serializador tem suporte interno para os seguintes tipos adicionais.

  • Half, Int128e UInt128 tipos numéricos.

    Console.WriteLine(JsonSerializer.Serialize(
        [ Half.MaxValue, Int128.MaxValue, UInt128.MaxValue ]
    ));
    // [65500,170141183460469231731687303715884105727,340282366920938463463374607431768211455]
    
  • Memory<T> e ReadOnlyMemory<T> valores. byte valores são serializados para cadeias de caracteres Base64 e outros tipos para matrizes JSON.

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

Gerador de fonte

O .NET 8 inclui aprimoramentos do gerador de código-fonte System.Text.Json que visam tornar a experiência AOT nativa igual à do serializador baseado em reflexão. Por exemplo:

  • O gerador de código-fonte agora suporta tipos de serialização com required e init propriedades. Ambos já eram suportados na serialização baseada em reflexão.

  • Formatação melhorada do código-fonte.

  • JsonSourceGenerationOptionsAttribute paridade de recursos com JsonSerializerOptions. Para obter mais informações, consulte Especificar opções (geração de origem).

  • Diagnósticos adicionais (como SYSLIB1034 e SYSLIB1039).

  • Não inclua tipos de propriedades ignoradas ou inacessíveis.

  • Suporte para aninhamento JsonSerializerContext de declarações dentro de tipos arbitrários.

  • Suporte para tipos gerados pelo compilador ou indescritíveis em cenários de geração de código-fonte fracamente tipados. Como os tipos gerados pelo compilador não podem ser explicitamente especificados pelo gerador de código-fonte, System.Text.Json agora executa a resolução ancestral mais próxima em tempo de execução. Essa resolução determina o supertipo mais apropriado com o qual serializar o valor.

  • Novo tipo de JsonStringEnumConverter<TEnum>conversor . A classe existente JsonStringEnumConverter não é suportada na AOT nativa. Você pode anotar seus tipos de enum da seguinte maneira:

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

    Para obter mais informações, consulte Serializar campos de enum como cadeias de caracteres.

  • A nova JsonConverter.Type propriedade permite pesquisar o tipo de instância não genérica JsonConverter :

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

    A propriedade é anulável, pois retorna null para JsonConverterFactory instâncias e typeof(T) para JsonConverter<T> instâncias.

Geradores de fonte de cadeia

A JsonSerializerOptions classe inclui uma nova TypeInfoResolverChain propriedade que complementa a propriedade existente TypeInfoResolver . Essas propriedades são usadas na personalização de contratos para encadeamento de geradores de origem. A adição da nova propriedade significa que você não precisa especificar todos os componentes encadeados em um local de chamada — eles podem ser adicionados após o fato. TypeInfoResolverChain Também permite introspeccionar a cadeia ou remover componentes dela. Para obter mais informações, consulte Combinar geradores de origem.

Além disso, JsonSerializerOptions.AddContext<TContext>() está agora obsoleto. Foi substituído pelo TypeInfoResolver e TypeInfoResolverChain propriedades. Para obter mais informações, consulte SYSLIB0049.

Hierarquias de interface

O .NET 8 adiciona suporte para serializar propriedades de hierarquias de interface.

O código a seguir mostra um exemplo onde as propriedades da interface imediatamente implementada e sua interface base são serializadas.

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; }
}

Políticas de nomenclatura

JsonNamingPolicy Inclui novas políticas de nomenclatura para snake_case (com sublinhado) e kebab-case (com hífen) conversões de nome de propriedade. Use estas políticas de forma semelhante à política existente JsonNamingPolicy.CamelCase :

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

Para obter mais informações, consulte Usar uma política de nomenclatura interna.

Propriedades só de leitura

Agora você pode desserializar em campos ou propriedades somente leitura (ou seja, aqueles que não têm um set acessador).

Para aderir a este suporte globalmente, defina uma nova opção, PreferredObjectCreationHandling, para JsonObjectCreationHandling.Populate. Se a compatibilidade for uma preocupação, você também poderá habilitar a funcionalidade de forma mais granular colocando o [JsonObjectCreationHandling(JsonObjectCreationHandling.Populate)] atributo em tipos específicos cujas propriedades devem ser preenchidas ou em propriedades individuais.

Por exemplo, considere o código a seguir que desserializa em um CustomerInfo tipo que tem duas propriedades somente leitura.

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"
    };
}

Antes do .NET 8, os valores de entrada eram ignorados e as Names propriedades e Company mantinham seus valores padrão.

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

Agora, os valores de entrada são usados para preencher as propriedades somente leitura durante a desserialização.

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

Para obter mais informações sobre o comportamento de desserialização de preenchimento, consulte Preencher propriedades inicializadas.

Desativar padrão baseado em reflexão

Agora você pode desativar o uso do serializador baseado em reflexão por padrão. Essa desativação é útil para evitar o enraizamento acidental de componentes de reflexão que nem sequer estão em uso, especialmente em aplicativos AOT cortados e nativos. Para desabilitar a serialização baseada em reflexão padrão exigindo que um JsonSerializerOptions argumento seja passado para os JsonSerializer métodos de serialização e desserialização, defina a JsonSerializerIsReflectionEnabledByDefault propriedade MSBuild como false em seu arquivo de projeto.

Use a nova IsReflectionEnabledByDefault API para verificar o valor da opção de recurso. Se você for um autor de biblioteca construindo sobre o , poderá confiar na propriedade para configurar seus padrões sem enraizar acidentalmente os componentes de System.Text.Jsonreflexão.

Para obter mais informações, consulte Desabilitar padrões de reflexão.

Novos métodos de API JsonNode

Os JsonNode tipos e System.Text.Json.Nodes.JsonArray incluem os novos métodos a seguir.

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>();
}

Membros não públicos

Você pode optar por membros não públicos no contrato de serialização para um determinado tipo usando JsonIncludeAttribute anotações de atributo e JsonConstructorAttribute atributo.

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; }
}

Para obter mais informações, consulte Usar tipos imutáveis e membros e acessadores não públicos.

APIs de desserialização de streaming

O .NET 8 inclui novos IAsyncEnumerable<T> métodos de extensão de desserialização de streaming, por exemplo GetFromJsonAsAsyncEnumerable. Métodos semelhantes têm existido que retornam Task<TResult>, por exemplo, HttpClientJsonExtensions.GetFromJsonAsync. Os novos métodos de extensão invocam APIs de streaming e retornam IAsyncEnumerable<T>.

O código a seguir mostra como você pode usar os novos métodos de extensão.

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);

Método de extensão WithAddedModifier

O novo WithAddedModifier(IJsonTypeInfoResolver, Action<JsonTypeInfo>) método de extensão permite introduzir facilmente modificações nos contratos de serialização de instâncias arbitrárias IJsonTypeInfoResolver .

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

Novo JsonContent.Create sobrecargas

Agora você pode criar JsonContent instâncias usando contratos seguros para corte ou gerados na fonte. Os novos métodos são os seguintes:

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
{
}

Congelar uma instância JsonSerializerOptions

Os novos métodos a seguir permitem controlar quando uma JsonSerializerOptions instância é congelada:

  • JsonSerializerOptions.MakeReadOnly()

    Essa sobrecarga foi projetada para ser segura para corte e, portanto, lançará uma exceção nos casos em que a instância de opções não tiver sido configurada com um resolvedor.

  • JsonSerializerOptions.MakeReadOnly(Boolean)

    Se você passar true para essa sobrecarga, ele preencherá a instância de opções com o resolvedor de reflexão padrão, se estiver faltando. Este método está marcado RequiresUnreferenceCode/RequiresDynamicCode e, portanto, não é adequado para aplicativos AOT nativos.

A nova IsReadOnly propriedade permite verificar se a instância de opções está congelada.

Abstração temporal

A nova TimeProvider classe e ITimer interface adicionam a funcionalidade de abstração de tempo, que permite simular o tempo em cenários de teste. Além disso, você pode usar a abstração de tempo para simular Task operações que dependem da progressão de tempo usando Task.Delay e Task.WaitAsync. A abstração de tempo suporta as seguintes operações de tempo essenciais:

  • Recuperar hora local e UTC
  • Obter um carimbo de data/hora para medir o desempenho
  • Criar um temporizador

O trecho de código a seguir mostra alguns exemplos de uso.

// 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);
}

Melhorias UTF8

Se você quiser habilitar a gravação de uma representação semelhante a uma cadeia de caracteres do seu tipo em uma extensão de destino, implemente a nova IUtf8SpanFormattable interface em seu tipo. Esta nova interface está intimamente relacionada com ISpanFormattable, mas tem como alvo UTF8 e Span<byte> em vez de UTF16 e Span<char>.

IUtf8SpanFormattable foi implementado em todos os tipos primitivos (mais outros), com exatamente a mesma lógica compartilhada, seja direcionando string, Span<char>, ou Span<byte>. Tem suporte total para todos os formatos (incluindo o novo especificador binário "B") e todas as culturas. Isso significa que agora você pode formatar diretamente para UTF8 de Byte, Complex, Char, DateOnly, DateTimeOffsetUInt32HalfGuidIPAddressDoubleDecimalDateTimeIPNetworkInt16SByteNFloatSingleIntPtrRuneInt128TimeOnlyTimeSpanInt64Int32UInt64UInt128UInt16UIntPtre .Version

Novos Utf8.TryWrite métodos fornecem uma contrapartida baseada em UTF8 para os métodos existentes MemoryExtensions.TryWrite , que são baseados em UTF16. Você pode usar sintaxe de cadeia de caracteres interpolada para formatar uma expressão complexa diretamente em um intervalo de bytes UTF8, por exemplo:

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);

A implementação reconhece IUtf8SpanFormattable os valores de formato e usa suas implementações para gravar suas representações UTF8 diretamente na extensão de destino.

A implementação também utiliza o novo Encoding.TryGetBytes(ReadOnlySpan<Char>, Span<Byte>, Int32) método, que juntamente com seu Encoding.TryGetChars(ReadOnlySpan<Byte>, Span<Char>, Int32) homólogo, suporta codificação e decodificação em uma extensão de destino. Se a extensão não for longa o suficiente para manter o estado resultante, os métodos retornarão false em vez de lançar uma exceção.

Métodos para trabalhar com aleatoriedade

Os System.Random tipos e System.Security.Cryptography.RandomNumberGenerator introduzem dois novos métodos para trabalhar com aleatoriedade.

GetItems<T>()

Os novos System.Random.GetItems métodos e System.Security.Cryptography.RandomNumberGenerator.GetItems permitem que você escolha aleatoriamente um número especificado de itens de um conjunto de entrada. O exemplo a seguir mostra como usar System.Random.GetItems<T>() (na instância fornecida pela Random.Shared propriedade) para inserir aleatoriamente 31 itens em uma matriz. Este exemplo poderia ser usado em um jogo de "Simon" onde os jogadores devem se lembrar de uma sequência de botões coloridos.

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 ...

Embaralhar<T>()

Os novos Random.Shuffle e RandomNumberGenerator.Shuffle<T>(Span<T>) métodos permitem randomizar a ordem de uma extensão. Esses métodos são úteis para reduzir o viés de treinamento no aprendizado de máquina (então a primeira coisa nem sempre é treinar, e a última coisa sempre testar).

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);
// ...

Tipos focados no desempenho

O .NET 8 introduz vários novos tipos destinados a melhorar o desempenho do aplicativo.

  • O novo System.Collections.Frozen namespace inclui os tipos FrozenDictionary<TKey,TValue> de coleção e FrozenSet<T>. Esses tipos não permitem nenhuma alteração em chaves e valores depois que uma coleção é criada. Esse requisito permite operações de leitura mais rápidas (por exemplo, TryGetValue()). Esses tipos são particularmente úteis para coleções que são preenchidas na primeira utilização e depois persistem durante a duração de um serviço de longa duração, por exemplo:

    private static readonly FrozenDictionary<string, bool> s_configurationData =
        LoadConfigurationData().ToFrozenDictionary(optimizeForReads: true);
    
    // ...
    if (s_configurationData.TryGetValue(key, out bool setting) && setting)
    {
        Process();
    }
    
  • Métodos como MemoryExtensions.IndexOfAny procurar a primeira ocorrência de qualquer valor na coleção passada. O novo System.Buffers.SearchValues<T> tipo é projetado para ser passado para esses métodos. Correspondentemente, o .NET 8 adiciona novas sobrecargas de métodos como MemoryExtensions.IndexOfAny os que aceitam uma instância do novo tipo. Quando você cria uma instância do SearchValues<T>, todos os dados necessários para otimizar as pesquisas subsequentes são derivados naquele momento, o que significa que o trabalho é feito antecipadamente.

  • O novo System.Text.CompositeFormat tipo é útil para otimizar cadeias de caracteres de formato que não são conhecidas em tempo de compilação (por exemplo, se a cadeia de caracteres de formato for carregada de um arquivo de recurso). Um pouco de tempo extra é gasto antecipadamente para fazer o trabalho, como analisar a cadeia de caracteres, mas evita que o trabalho seja feito em cada uso.

    private static readonly CompositeFormat s_rangeMessage =
        CompositeFormat.Parse(LoadRangeMessageResource());
    
    // ...
    static string GetMessage(int min, int max) =>
        string.Format(CultureInfo.InvariantCulture, s_rangeMessage, min, max);
    
  • Novos System.IO.Hashing.XxHash3 e System.IO.Hashing.XxHash128 tipos fornecem implementações dos rápidos algoritmos de hash XXH3 e XXH128.

System.Numerics e System.Runtime.Intrinsics

Esta seção aborda melhorias nos System.Numerics namespaces e System.Runtime.Intrinsics .

  • Vector256<T>, Matrix3x2e Matrix4x4 melhoraram a aceleração de hardware no .NET 8. Por exemplo, Vector256<T> foi reimplementado para ser 2x Vector128<T> operações internas, sempre que possível. Isso permite a aceleração parcial de algumas funções quando Vector128.IsHardwareAccelerated == true mas Vector256.IsHardwareAccelerated == false, como no Arm64.
  • As intrínsecas de hardware agora são anotadas com o ConstExpected atributo. Isso garante que os usuários estejam cientes quando o hardware subjacente espera uma constante e, portanto, quando um valor não constante pode prejudicar inesperadamente o desempenho.
  • A Lerp(TSelf, TSelf, TSelf)Lerp API foi adicionada a IFloatingPointIeee754<TSelf> (Single), double (floatDouble) e Half. Esta API permite que uma interpolação linear entre dois valores seja executada de forma eficiente e correta.

Vetor512 e AVX-512

O .NET Core 3.0 expandiu o suporte a SIMD para incluir as APIs intrínsecas de hardware específicas da plataforma para x86/x64. O .NET 5 adicionou suporte para Arm64 e o .NET 7 adicionou as intrínsecas de hardware multiplataforma. O .NET 8 promove o suporte a SIMD introduzindo Vector512<T> e suportando instruções Intel Advanced Vetor Extensions 512 (AVX-512).

Especificamente, o .NET 8 inclui suporte para os seguintes recursos principais do AVX-512:

  • Operações vetoriais de 512 bits
  • 16 registos SIMD adicionais
  • Instruções adicionais disponíveis para vetores de 128 bits, 256 bits e 512 bits

Se você tiver hardware que suporte a funcionalidade, então Vector512.IsHardwareAccelerated agora relatórios true.

O .NET 8 também adiciona várias classes específicas da plataforma sob o System.Runtime.Intrinsics.X86 namespace:

Essas classes seguem a mesma forma geral que outras arquiteturas de conjunto de instruções (ISAs), na medida em que expõem uma IsSupported propriedade e uma classe aninhada Avx512F.X64 para instruções disponíveis apenas para processos de 64 bits. Além disso, cada classe tem uma classe aninhada Avx512F.VL que expõe as extensões (comprimento do Avx512VL vetor) para o conjunto de instruções correspondente.

Mesmo que não utilize Vector512explicitamente instruções específicas ou Avx512Fespecíficas no seu código, provavelmente continuará a beneficiar do novo suporte AVX-512. A EIC pode tirar partido dos registos e instruções adicionais implicitamente ao utilizar Vector128<T> ou Vector256<T>. A biblioteca de classes base usa esses intrínsecos de hardware internamente na maioria das operações expostas por Span<T> e ReadOnlySpan<T> em muitas das APIs matemáticas expostas para os tipos primitivos.

Validação de dados

O System.ComponentModel.DataAnnotations namespace inclui novos atributos de validação de dados destinados a cenários de validação em serviços nativos da nuvem. Enquanto os validadores pré-existentes DataAnnotations são voltados para a validação típica de entrada de dados da interface do usuário, como campos em um formulário, os novos atributos são projetados para validar dados que não são de entrada do usuário, como opções de configuração. Além dos novos atributos, novas propriedades foram adicionadas aos RangeAttribute tipos e RequiredAttribute .

Nova API Description
RangeAttribute.MinimumIsExclusive
RangeAttribute.MaximumIsExclusive
Especifica se os limites estão incluídos no intervalo permitido.
System.ComponentModel.DataAnnotations.LengthAttribute Especifica os limites inferior e superior para cadeias de caracteres ou coleções. Por exemplo, [Length(10, 20)] requer pelo menos 10 elementos e no máximo 20 elementos em uma coleção.
System.ComponentModel.DataAnnotations.Base64StringAttribute Valida que uma cadeia de caracteres é uma representação Base64 válida.
System.ComponentModel.DataAnnotations.AllowedValuesAttribute
System.ComponentModel.DataAnnotations.DeniedValuesAttribute
Especifique listas de permissões e listas de negação, respectivamente. Por exemplo, [AllowedValues("apple", "banana", "mango")].

Métricas

As novas APIs permitem anexar tags de par chave-valor e Instrument objetos ao Meter criá-los. Os agregadores de medições métricas publicadas podem usar as tags para diferenciar os valores agregados.

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);

As novas APIs incluem:

Criptografia

O .NET 8 adiciona suporte para as primitivas de hash SHA-3. (SHA-3 é atualmente suportado pelo Linux com OpenSSL 1.1.1 ou posterior e Windows 11 Build 25324 ou posterior.) As APIs onde o SHA-2 está disponível agora oferecem um complemento SHA-3. Isso inclui SHA3_256, SHA3_384, e SHA3_512 para hashing; HMACSHA3_256, HMACSHA3_384, e HMACSHA3_512 para HMAC; HashAlgorithmName.SHA3_256, HashAlgorithmName.SHA3_384, e HashAlgorithmName.SHA3_512 para hashing onde o algoritmo é configurável; e RSAEncryptionPadding.OaepSHA3_256, RSAEncryptionPadding.OaepSHA3_384, e RSAEncryptionPadding.OaepSHA3_512 para criptografia RSA OAEP.

O exemplo a seguir mostra como usar as APIs, incluindo a SHA3_256.IsSupported propriedade para determinar se a plataforma suporta 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
{
    // ...
}

O suporte a SHA-3 destina-se atualmente a suportar primitivos criptográficos. Construções e protocolos de nível superior não devem suportar totalmente o SHA-3 inicialmente. Esses protocolos incluem certificados SignedXmlX.509 e COSE.

Rede

Suporte para proxy HTTPS

Até agora, os tipos de proxy que HttpClient suportavam todos permitiam que um "man-in-the-middle" visse a qual site o cliente está se conectando, mesmo para URIs HTTPS. HttpClient agora suporta proxy HTTPS, que cria um canal criptografado entre o cliente e o proxy para que todas as solicitações possam ser tratadas com total privacidade.

Para habilitar o proxy HTTPS, defina a variável de all_proxy ambiente ou use a WebProxy classe para controlar o proxy programaticamente.

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

Você também pode usar a WebProxy classe para controlar o proxy programaticamente.

Métodos ZipFile baseados em fluxo

O .NET 8 inclui novas sobrecargas que ZipFile.CreateFromDirectory permitem coletar todos os arquivos incluídos em um diretório e compactá-los e, em seguida, armazenar o arquivo zip resultante no fluxo fornecido. Da mesma forma, novas ZipFile.ExtractToDirectory sobrecargas permitem que você forneça um fluxo contendo um arquivo compactado e extraia seu conteúdo para o sistema de arquivos. Estas são as novas sobrecargas:

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) { }
}

Essas novas APIs podem ser úteis quando o espaço em disco é limitado, porque evitam ter que usar o disco como uma etapa intermediária.

Bibliotecas de extensão

Esta seção contém os seguintes subtópicos:

Serviços de DI com chave

Os serviços de injeção de dependência com chave (DI) fornecem um meio para registrar e recuperar serviços de DI usando chaves. Usando chaves, você pode definir o escopo de como registrar e consumir serviços. Estas são algumas das novas APIs:

O exemplo a seguir mostra como usar serviços DI com chave.

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.";
}

Para obter mais informações, consulte dotnet/runtime#64427.

Serviços de ciclo de vida hospedados

Os serviços hospedados agora têm mais opções para execução durante o ciclo de vida do aplicativo. IHostedService fornecido StartAsync e StopAsync, e agora IHostedLifecycleService fornece estes métodos adicionais:

Esses métodos são executados antes e depois dos pontos existentes, respectivamente.

O exemplo a seguir mostra como usar as novas APIs.

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;
    }
}

Para obter mais informações, consulte dotnet/runtime#86511.

Validação de opções

Gerador de fonte

Para reduzir a sobrecarga de inicialização e melhorar o conjunto de recursos de validação, introduzimos um gerador de código-fonte que implementa a lógica de validação. O código a seguir mostra modelos de exemplo e classes de validador.

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>
{
}

Se seu aplicativo usa injeção de dependência, você pode injetar a validação conforme mostrado no código de exemplo a seguir.

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>();

Tipo ValidateOptionsResultBuilder

O .NET 8 introduz o ValidateOptionsResultBuilder tipo para facilitar a criação de um ValidateOptionsResult objeto. É importante ressaltar que esse construtor permite o acúmulo de vários erros. Anteriormente, a criação do ValidateOptionsResult objeto necessário para implementar IValidateOptions<TOptions>.Validate(String, TOptions) era difícil e, às vezes, resultava em erros de validação em camadas. Se houvesse vários erros, o processo de validação geralmente parava no primeiro erro.

O trecho de código a seguir mostra um exemplo de uso do 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 construtores

LoggerMessageAttribute agora oferece sobrecargas adicionais do construtor. Anteriormente, você tinha que escolher o construtor sem parâmetros ou o construtor que exigia todos os parâmetros (ID do evento, nível de log e mensagem). As novas sobrecargas oferecem maior flexibilidade na especificação dos parâmetros necessários com código reduzido. Se você não fornecer um ID de evento, o sistema gerará um automaticamente.

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

Métricas de extensões

Interface IMeterFactory

Você pode registrar a nova IMeterFactory interface em contêineres de injeção de dependência (DI) e usá-la para criar Meter objetos de maneira isolada.

Registre o IMeterFactory no contêiner DI usando a implementação padrão de fábrica do medidor:

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

Os consumidores podem então obter a fábrica do medidor e usá-lo para criar um novo Meter objeto.

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

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

Meter meter = meterFactory.Create(options);

Classe T> do MetricCollector<

A nova MetricCollector<T> classe permite registrar medidas métricas junto com carimbos de data/hora. Além disso, a classe oferece a flexibilidade de usar um provedor de tempo de sua escolha para a geração precisa de carimbos de data/hora.

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

O pacote NuGet System.Numerics.Tensors atualizado inclui APIs no novo TensorPrimitives namespace que adicionam suporte para operações tensoras. As primitivas tensor otimizam cargas de trabalho com uso intensivo de dados, como as de IA e aprendizado de máquina.

Cargas de trabalho de IA, como pesquisa semântica e geração aumentada de recuperação (RAG), estendem os recursos de linguagem natural de modelos de linguagem grandes, como o ChatGPT, aumentando os prompts com dados relevantes. Para essas cargas de trabalho, as operações em vetores — como a semelhança de cosseno para encontrar os dados mais relevantes para responder a uma pergunta — são cruciais. O pacote System.Numerics.Tensors.TensorPrimitives fornece APIs para operações vetoriais, o que significa que você não precisa assumir uma dependência externa ou escrever sua própria implementação.

Este pacote substitui o pacote System.Numerics.Tentors.

Para obter mais informações, consulte a postagem do blog Anunciando o .NET 8 RC 2.

Consulte também