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 LibraryImport
mé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 GeneratedComInterface
tipos -atribuídos e GeneratedComClass
-tipos atribuídos.
Se seu código C# usa apenas uma GeneratedComInterface
interface -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 GeneratedComInterfaceAttribute
o , 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
- Serialização
- Abstração temporal
- Melhorias UTF8
- Métodos para trabalhar com aleatoriedade
- Tipos focados no desempenho
- System.Numerics e System.Runtime.Intrinsics
- Validação de dados
- Métricas
- Criptografia
- Rede
- Métodos ZipFile baseados em fluxo
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:
- Suporte integrado para tipos adicionais
- Gerador de fonte
- Hierarquias de interface
- Políticas de nomenclatura
- Propriedades só de leitura
- Desativar padrão baseado em reflexão
- Novos métodos de API JsonNode
- Membros não públicos
- APIs de desserialização de streaming
- Método de extensão WithAddedModifier
- Novo JsonContent.Create sobrecargas
- Congelar uma instância JsonSerializerOptions
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
einit
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éricaJsonConverter
:Dictionary<Type, JsonConverter> CreateDictionary(IEnumerable<JsonConverter> converters) => converters.Where(converter => converter.Type != null) .ToDictionary(converter => converter.Type!);
A propriedade é anulável, pois retorna
null
paraJsonConverterFactory
instâncias etypeof(T)
paraJsonConverter<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:
- JsonContent.Create(Object, JsonTypeInfo, MediaTypeHeaderValue)
- JsonContent.Create<T>(T, JsonTypeInfo<T>, MediaTypeHeaderValue)
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á marcadoRequiresUnreferenceCode
/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
, DateTimeOffset
UInt32
Half
Guid
IPAddress
Double
Decimal
DateTime
IPNetwork
Int16
SByte
NFloat
Single
IntPtr
Rune
Int128
TimeOnly
TimeSpan
Int64
Int32
UInt64
UInt128
UInt16
UIntPtr
e .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 quandoVector128.IsHardwareAccelerated == true
masVector256.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
(float
Double) 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:
- Avx512F (fundacional)
- Avx512BW (byte e palavra)
- Avx512CD (deteção de conflitos)
- Avx512DQ (palavra dupla e palavra quadriciclo)
- Avx512Vbmi (instruções de manipulação de bytes vetoriais)
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 Vector512
explicitamente instruções específicas ou Avx512F
especí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:
- MeterOptions
- Meter(MeterOptions)
- CreateCounter<T>(String, String, String, IEnumerable<KeyValuePair<String,Object>>)
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:
- Validação de opções
- LoggerMessageAttribute construtores
- Métricas de extensões
- Serviços de ciclo de vida hospedados
- Serviços de DI com chave
- System.Numerics.Tensors.TensorPrimitives
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:
- A IKeyedServiceProvider interface.
- O ServiceKeyAttribute atributo, que pode ser usado para injetar a chave que foi usada para registro/resolução no construtor.
- O FromKeyedServicesAttribute atributo, que pode ser usado em parâmetros do construtor de serviço para especificar qual serviço com chave usar.
- Vários novos métodos de extensão para IServiceCollection suportar serviços chaveados, por exemplo, ServiceCollectionServiceExtensions.AddKeyedScoped.
- A ServiceProvider implementação do IKeyedServiceProvider.
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:
- StartingAsync(CancellationToken)
- StartedAsync(CancellationToken)
- StoppingAsync(CancellationToken)
- StoppedAsync(CancellationToken)
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.