O que há de novo no tempo de execução do .NET 8
Este artigo descreve novas funcionalidades no runtime do .NET para o .NET 8.
Melhorias de desempenho
O .NET 8 inclui melhorias na geração de código e compilação em tempo de execução (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 no desempenho 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 zerar, se puder determinar os 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 melhora o desempenho em aproximadamente 15%. Num conjunto de testes de benchmark de ~4600, 23% tiveram 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 a geração de código que generaliza a capacidade do JIT de promover variáveis de estrutura. Esta otimização (também chamada 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 elimina essas limitações, corrigindo assim uma série de problemas de longa data do JIT.
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 API RefreshMemoryLimit() 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 a redução de escala for muito agressiva e não deixar espaço para o GC manobrar. Nesse caso, considere chamar
GC.Collect(2, GCCollectionMode.Aggressive)
para reduzir o uso atual de memória 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 chamada de
RefreshMemoryLimit
será bem-sucedida, mas não poderá 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 fixa o limite rígido de heap 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
As aplicações móveis no iOS, tvOS e MacCatalyst podem optar por um novo modo de globalização híbrido que usa um pacote ICU mais leve. No modo híbrido, os dados de globalização são parcialmente obtidos do pacote ICU e parcialmente de chamadas para APIs nativas. O modo híbrido serve todas as localizações 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 propriedade HybridGlobalization
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 a partir do código-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 permitir a chamada de código não gerido para o C#. Este gerador de origem integra-se com o LibraryImportAttribute, e poderá usar tipos com o GeneratedComInterfaceAttribute como parâmetros e tipos de retorno em métodos atribuídos a LibraryImport
.
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.Marshalling;
[GeneratedComInterface]
[Guid("5401c312-ab23-4dd3-aa40-3cb4b3a4683e")]
partial interface IComInterface
{
void DoWork();
}
internal partial class MyNativeLib
{
[LibraryImport(nameof(MyNativeLib))]
public static partial void GetComInterface(out IComInterface comInterface);
}
O gerador de código-fonte também suporta o novo atributo GeneratedComClassAttribute para permitir que você passe tipos que implementam interfaces com o atributo GeneratedComInterfaceAttribute para código 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 atributo GeneratedComInterfaceAttribute suportam todos os mesmos tipos que LibraryImportAttribute
, e LibraryImportAttribute
agora suporta tipos atribuídos pelos atributos GeneratedComInterface
e GeneratedComClass
.
Se seu código C# usa apenas uma interface atribuída a GeneratedComInterface
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 propriedade Options 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 tipo de StrategyBasedComWrappers para criar e gerenciar os wrappers de objeto COM e os wrappers de objeto gerenciado. Este novo tipo fornece a experiência de utilizador .NET esperada para a interoperabilidade entre COM, ao mesmo tempo que oferece pontos de personalização para utilizadores 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 suporta atualmente, considere usar o novo tipo de StrategyBasedComWrappers 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 para interoperabilidade gerada por código-fonte. A correção altera a interface para usar o atributo GeneratedComInterfaceAttribute. E ao lado de cada classe que implementa uma interface com GeneratedComInterfaceAttribute
, uma lâmpada oferece uma opção para adicionar o atributo GeneratedComClassAttribute ao tipo. Assim que os seus tipos forem convertidos, pode mover os seus métodos DllImport
para usar LibraryImportAttribute
.
Limitações
O gerador de origem COM não suporta afinidade de apartamento, usando a palavra-chave new
para ativar um COM CoClass e as seguintes APIs:
- Interfaces baseadas em IDispatch.
- IInspectable- interfaces baseadas em.
- Propriedades e eventos COM.
Gerador de código-fonte de vinculação de configuração
O .NET 8 introduz um gerador de código fonte no ASP.NET Core para fornecer configuração e compatíveis com AOT e recorte. O gerador é uma alternativa à implementação baseada em reflexão pré-existente.
O gerador de origem investiga chamadas para Configure(TOptions), Binde Get a fim de 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 é habilitado por padrão em aplicativos Web compilados pela AOT e quando PublishTrimmed
está definido como true
(aplicativos .NET 8+). Para outros tipos de projeto, o gerador de código-fonte está desativado por padrão, mas você pode optar por participar definindo a propriedade EnableConfigurationBindingGenerator
para true
em seu arquivo de projeto:
<PropertyGroup>
<EnableConfigurationBindingGenerator>true</EnableConfigurationBindingGenerator>
</PropertyGroup>
O código a seguir mostra um exemplo de como invocar o vinculador.
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
- Aperfeiçoamentos para o UTF8
- Métodos para trabalhar com aleatoriedade
- Tipos focados no desempenho
- System.Numerics e System.Runtime.Intrinsics
- Validação de dados
- Métricas
- Criptografia
- Redes
- 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 aplicar reflexão sobre um ponteiro de função, por exemplo, typeof(delegate*<void>())
ou FieldInfo.FieldType
respetivamente, um IntPtr foi retornado. A partir do .NET 8, um objeto System.Type é 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.
Observação
Uma instância de ponteiro de função, que é um endereço físico para uma função, continua a ser representada como um IntPtr. Apenas o tipo de reflexão mudou.
Atualmente, a nova funcionalidade é implementada apenas no runtime do CoreCLR e no MetadataLoadContext.
Novas APIs foram adicionadas a 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 para System.Text.Json funcionalidade de serialização e desserialização no .NET 8. Por exemplo, você pode personalizar a manipulação de propriedades JSON que não estão no POCO.
As seções a seguir descrevem outros aprimoramentos de serialização:
- Suporte integrado para tipos adicionais
- Gerador de código-fonte
- Hierarquias de interface
- Políticas de nomenclatura
- Propriedades somente leitura
- Desativar padrão baseado em reflexão
- Novos métodos de API JsonNode
- Membros não públicos
- APIs de deserialização de streaming
- Método de extensão WithAddedModifier
- Novo JsonContent.Criar sobrecargas
- Congele 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]
Valores de Memory<T> e ReadOnlyMemory<T>. Os valores
byte
são serializados em cadeias de caracteres Base64 e outros tipos em 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 do System.Text.Json que visam tornar a experiência de do AOT nativo equivalente à do serializador baseado em reflexão. Por exemplo:
O gerador de código-fonte agora suporta tipos de serialização com propriedades
required
einit
. 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 o aninhamento de declarações
JsonSerializerContext
dentro de tipos arbitrários.Suporte para tipos gerados por compilador ou tipos indizí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 do ancestral mais próximo em tempo de execução. Essa resolução determina o supertipo mais apropriado com o qual serializar o valor.
Novo tipo de conversor
JsonStringEnumConverter<TEnum>
. A classe JsonStringEnumConverter existente 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 propriedade
JsonConverter.Type
permite consultar o tipo de uma instância não genérica deJsonConverter
: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 instânciasJsonConverterFactory
etypeof(T)
para instânciasJsonConverter<T>
.
Geradores de código fonte de cadeia
A classe JsonSerializerOptions inclui uma nova propriedade TypeInfoResolverChain que complementa a propriedade TypeInfoResolver existente. Essas propriedades são usadas na personalização de contratos para encadeamento de geradores de código-fonte. A adição da nova propriedade significa que não precisas especificar todos os componentes encadeados no local da chamada — podem ser adicionados posteriormente. TypeInfoResolverChain também permite introspeccionar a cadeia ou remover componentes dela. Para obter mais informações, consulte Combinar geradores de fonte.
Além disso, JsonSerializerOptions.AddContext<TContext>() está agora obsoleta. Foi substituído pelas propriedades TypeInfoResolver e TypeInfoResolverChain. 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 as conversões dos nomes de propriedade snake_case
(com sublinhado) e kebab-case
(com hífen). Use estas políticas à semelhança da política JsonNamingPolicy.CamelCase existente.
var options = new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower
};
JsonSerializer.Serialize(new { PropertyName = "value" }, options);
// { "property_name" : "value" }
Para obter mais informações, consulte Utilize uma política de nomenclatura incorporada.
Propriedades somente leitura
Agora é possível desserializar em campos ou propriedades de leitura apenas (ou seja, aqueles que não têm um método de acesso set
).
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 pode habilitar a funcionalidade de forma mais granular colocando o atributo [JsonObjectCreationHandling(JsonObjectCreationHandling.Populate)]
em tipos específicos cujas propriedades devem ser preenchidas ou em propriedades individuais.
Por exemplo, considere o seguinte código que desserializa para um tipo CustomerInfo
que tem duas propriedades somente de 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 propriedades Names
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 do ao popular com, consulte Propriedades inicializadas ao popular.
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 argumento JsonSerializerOptions seja passado para os métodos de serialização e desserialização JsonSerializer, defina a propriedade MSBuild JsonSerializerIsReflectionEnabledByDefault
para false
no arquivo de projeto.
Use a nova API IsReflectionEnabledByDefault para verificar o valor da opção de recurso. Se você for um autor de biblioteca construindo sobre System.Text.Json, poderá confiar na propriedade para configurar seus padrões sem enraizar acidentalmente os componentes de reflexão.
Para obter mais informações, consulte Desativar padrões de reflexão.
Novos métodos de API JsonNode
Os tipos JsonNode 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
Pode incluir membros não públicos no contrato de serialização para um determinado tipo, usando os atributos de anotação JsonIncludeAttribute e JsonConstructorAttribute.
public static void NonPublicMembers()
{
string json = JsonSerializer.Serialize(new MyPoco(42));
Console.WriteLine(json);
// {"X":42}
JsonSerializer.Deserialize<MyPoco>(json);
}
public class MyPoco
{
[JsonConstructor]
internal MyPoco(int x) => X = x;
[JsonInclude]
internal int X { get; }
}
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 métodos de extensão de desserialização de streaming de IAsyncEnumerable<T>, por exemplo, GetFromJsonAsAsyncEnumerable. Existem métodos semelhantes 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 método de extensão WithAddedModifier(IJsonTypeInfoResolver, Action<JsonTypeInfo>) permite introduzir facilmente modificações nos contratos de serialização de instâncias de IJsonTypeInfoResolver
arbitrárias.
var options = new JsonSerializerOptions
{
TypeInfoResolver = MyContext.Default
.WithAddedModifier(static typeInfo =>
{
foreach (JsonPropertyInfo prop in typeInfo.Properties)
{
prop.Name = prop.Name.ToUpperInvariant();
}
})
};
Novas sobrecargas para JsonContent.Create
Agora você pode criar instâncias de JsonContent usando contratos seguros para corte ou gerados pela 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 do JsonSerializerOptions
Os novos métodos a seguir permitem controlar quando uma instância JsonSerializerOptions é 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, ela preencherá a instância de opções com o resolvedor de reflexão padrão, se faltar um. Este método está marcadoRequiresUnreferenceCode
/RequiresDynamicCode
e, portanto, não é adequado para aplicativos AOT nativos.
A nova propriedade IsReadOnly permite verificar se a instância de opções está congelada.
Abstração temporal
A nova classe TimeProvider e a interface ITimer adicionam abstração de tempo funcionalidade, que permite simular o tempo em cenários de teste. Além disso, você pode usar a abstração de tempo para simular operações de Task 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 timestamp 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 UTF-8
Se pretender permitir a gravação de uma representação similar a uma string do seu tipo num span de destino, implemente a nova interface IUtf8SpanFormattable no 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 (além de outros), com exatamente a mesma lógica compartilhada, seja visando 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
, DateTime
, DateTimeOffset
, Decimal
, Double
, Guid
, Half
, IPAddress
, IPNetwork
, Int16
, Int32
, Int64
, Int128
, IntPtr
, NFloat
, SByte
, Single
, Rune
, TimeOnly
, TimeSpan
, UInt16
, UInt32
, UInt64
, UInt128
, UIntPtr
e Version
.
Novos métodos Utf8.TryWrite fornecem uma contrapartida baseada em UTF8 para os métodos de MemoryExtensions.TryWrite existentes, 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 nos valores de formato e usa suas implementações para escrever suas representações UTF8 diretamente na extensão de destino.
A implementação também utiliza o novo método Encoding.TryGetBytes(ReadOnlySpan<Char>, Span<Byte>, Int32), que, juntamente com sua contraparte Encoding.TryGetChars(ReadOnlySpan<Byte>, Span<Char>, Int32), 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 tipos System.Random e System.Security.Cryptography.RandomNumberGenerator introduzem dois novos métodos para trabalhar com aleatoriedade.
GetItems<T>()
Os novos métodos System.Random.GetItems e System.Security.Cryptography.RandomNumberGenerator.GetItems permitem escolher aleatoriamente um número especificado de itens de um conjunto de entradas. O exemplo a seguir mostra como usar System.Random.GetItems<T>()
(na instância fornecida pela propriedade Random.Shared) 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 métodos Random.Shuffle e RandomNumberGenerator.Shuffle<T>(Span<T>) 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 namespace System.Collections.Frozen inclui os tipos de coleção FrozenDictionary<TKey,TValue> e FrozenSet<T>. Esses tipos não permitem alterações 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(); // ... if (s_configurationData.TryGetValue(key, out bool setting) && setting) { Process(); }
Métodos como MemoryExtensions.IndexOfAny procuram a primeira ocorrência de qualquer valor na coleção passada. O novo tipo de System.Buffers.SearchValues<T> foi concebido para ser passado para esses métodos. Correspondentemente, o .NET 8 adiciona novas sobrecargas de métodos como MemoryExtensions.IndexOfAny que aceitam uma instância do novo tipo. Quando se cria uma instância de SearchValues<T>, todos os dados necessários para otimizar as pesquisas subsequentes são derivados nesse momento, o que significa que o trabalho é feito antecipadamente.
O novo tipo de System.Text.CompositeFormat é ú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 inicialmente para realizar trabalhos como a análise da string, mas previne que isso precise ser repetido em cada utilização.
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 tipos de System.IO.Hashing.XxHash3 e System.IO.Hashing.XxHash128 fornecem implementações dos rápidos algoritmos de hash XXH3 e XXH128.
System.Numerics e System.Runtime.Intrinsics
Esta seção aborda melhorias nos namespaces System.Numerics e System.Runtime.Intrinsics.
-
Vector256<T>, Matrix3x2e Matrix4x4 melhoraram a aceleração de hardware no .NET 8. Por exemplo, o Vector256<T> foi reimplementado internamente como operações
2x Vector128<T>
, 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 atributo
ConstExpected
. 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 API Lerp(TSelf, TSelf, TSelf)
Lerp
foi adicionada ao IFloatingPointIeee754<TSelf> e, portanto, aofloat
(Single),double
(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 suporte para Intel Advanced Vetor Extensions 512 (AVX-512) instruções.
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 tiver hardware que suporte a funcionalidade, Vector512.IsHardwareAccelerated agora reporta true
.
O .NET 8 também adiciona várias classes específicas da plataforma sob o namespace System.Runtime.Intrinsics.X86:
- Avx512F (fundamental)
- Avx512BW (byte e palavra)
- Avx512CD (deteção de conflitos)
- Avx512DQ (palavra dupla e palavra quádrupla)
- 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 propriedade IsSupported e uma classe Avx512F.X64 aninhada para instruções disponíveis apenas para processos de 64 bits. Além disso, cada classe tem uma classe Avx512F.VL aninhada que expõe as extensões Avx512VL
(comprimento do vetor) para o conjunto de instruções correspondente.
Mesmo que não utilize explicitamente as instruções específicas de Vector512
ou Avx512F
no seu código, provavelmente continuará a beneficiar do novo suporte AVX-512. O JIT pode tirar partido dos registos e das instruções adicionais implicitamente quando utiliza 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> e em muitas das APIs matemáticas expostas para os tipos primitivos.
Validação de dados
O namespace System.ComponentModel.DataAnnotations inclui novos atributos de validação de dados destinados a cenários de validação em serviços nativos da nuvem. Enquanto os validadores de DataAnnotations
pré-existentes 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 ao tipo RangeAttribute.
Nova API | Descrição |
---|---|
RangeAttribute.MinimumIsExclusive RangeAttribute.MaximumIsExclusive |
Especifica se os limites estão incluídos no intervalo permitido. |
System.ComponentModel.DataAnnotations.LengthAttribute | Especifica limites superior e inferior 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 a objetos Meter e Instrument ao 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 hash 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 propriedade SHA3_256.IsSupported
para determinar se a plataforma oferece suporte a 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 X.509, SignedXmle COSE.
Ligação em rede
Suporte para proxy HTTPS
Até agora, os tipos de proxy que o HttpClient suporta permitiam que um intruso visse a que site o cliente se está a ligar, mesmo para URIs HTTPS. HttpClient agora suporta de 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 ambiente all_proxy
ou use a classe WebProxy 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 classe WebProxy para controlar o proxy programaticamente.
Métodos ZipFile baseados em fluxo
O .NET 8 inclui novas sobrecargas de ZipFile.CreateFromDirectory que permitem recolher todos os ficheiros incluídos num diretório, compactá-los e, em seguida, armazenar o ficheiro zip resultante no fluxo fornecido. Da mesma forma, novos métodos de sobrecarga em ZipFile.ExtractToDirectory permitem fornecer um fluxo contendo um ficheiro compactado e extrair o seu conteúdo para o sistema de ficheiros. 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:
- O IKeyedServiceProvider interface.
- O atributo ServiceKeyAttribute, que pode ser usado para injetar a chave que foi usada para registro/resolução no construtor.
- O atributo FromKeyedServicesAttribute, que pode ser usado nos parâmetros do construtor de um serviço para especificar qual serviço identificado por chave usar.
- Vários novos métodos de extensão para suportar IServiceCollection com serviços com chave, por exemplo, ServiceCollectionServiceExtensions.AddKeyedScoped.
- A ServiceProvider implementação de 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 forneceu 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 apresenta o tipo ValidateOptionsResultBuilder para facilitar a criação de um objeto ValidateOptionsResult. É importante ressaltar que esse construtor permite o acúmulo de vários erros. Anteriormente, criar o objeto ValidateOptionsResult necessário para implementá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();
Construtores de LoggerMessageAttribute
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 menos código. 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 interface IMeterFactory em contêineres de injeção de dependência (DI) e usá-la para criar objetos Meter 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 de medidores e usá-la para criar um novo objeto Meter.
IMeterFactory meterFactory = serviceProvider.GetRequiredService<IMeterFactory>();
MeterOptions options = new MeterOptions("MeterName")
{
Version = "version",
};
Meter meter = meterFactory.Create(options);
Classe MetricCollector<T>
A nova classe MetricCollector<T> permite registrar medidas métricas junto com carimbos de data/hora. Além disso, a classe oferece a flexibilidade de usar um fornecedor de tempo de sua escolha para a geração precisa de marcação temporal.
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 atualizado System.Numerics.Tensors NuGet inclui APIs no novo tipo System.Numerics.Tensors.TensorPrimitives 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.
As cargas de trabalho de IA, como pesquisa semântica e geração aumentada através de recuperação de dados (RAG), ampliam as capacidades de linguagem natural de modelos de linguagem grandes, como o ChatGPT, acrescentando dados relevantes aos prompts. Para essas cargas de trabalho, as operações em vetores — como semelhança de cosseno encontrar os dados mais relevantes para responder a uma pergunta — são cruciais. O tipo TensorPrimitives fornece APIs para operações vetoriais.
Para obter mais informações, consulte a postagem do blog Anunciando o .NET 8 RC 2.
Suporte nativo a AOT
A opção de publicar como AOT nativo foi introduzida pela primeira vez no .NET 7. A publicação de um aplicativo com AOT nativo cria uma versão totalmente independente do seu aplicativo que não precisa de tempo de execução — tudo é incluído em um único arquivo. O .NET 8 traz as seguintes melhorias para a publicação AOT nativa:
Adiciona suporte para as arquiteturas x64 e Arm64 no macOS.
Reduz os tamanhos dos aplicativos AOT nativos no Linux em até 50%. A tabela a seguir mostra o tamanho de um aplicativo "Hello World" publicado com AOT nativo que inclui todo o tempo de execução do .NET no .NET 7 vs. .NET 8:
Sistema Operativo .NET 7 .NET 8 Linux x64 (com -p:StripSymbols=true
)3,76 MB 1,84 MB Windows x64 2,85 MB 1,77 MB Permite especificar uma preferência de otimização: tamanho ou velocidade. Por padrão, o compilador escolhe gerar código rápido enquanto está ciente do tamanho do aplicativo. No entanto, você pode usar a propriedade
<OptimizationPreference>
MSBuild para otimizar especificamente para um ou outro. Para obter mais informações, consulte Otimizar implantações de AOT.
Destine plataformas semelhantes ao iOS com AOT nativa
O .NET 8 inicia o trabalho para habilitar o suporte AOT nativo para plataformas semelhantes ao iOS. Agora você pode criar e executar aplicativos .NET iOS e .NET MAUI com AOT nativo nas seguintes plataformas:
ios
iossimulator
maccatalyst
tvos
tvossimulator
Os testes preliminares mostram que o tamanho do aplicativo no disco diminui em cerca de 35% para aplicativos .NET iOS que usam AOT nativo em vez de Mono. O tamanho do aplicativo no disco para aplicativos .NET MAUI iOS diminui até 50%. Além disso, o tempo de inicialização também é mais rápido. Os aplicativos .NET iOS têm cerca de 28% tempo de inicialização mais rápido, enquanto os aplicativos .NET MAUI iOS têm cerca de 50% melhor desempenho de inicialização em comparação com o Mono. O suporte ao .NET 8 é experimental e apenas a primeira etapa para o recurso como um todo. Para obter mais informações, consulte a postagem do blog .NET 8 Performance Improvements in .NET MAUI.
O suporte nativo a AOT está disponível como um recurso opcional destinado à implantação de aplicativos; Mono ainda é o tempo de execução padrão para desenvolvimento e implantação de aplicativos. Para criar e executar um aplicativo .NET MAUI com AOT nativo em um dispositivo iOS, use dotnet workload install maui
para instalar a carga de trabalho do .NET MAUI e dotnet new maui -n HelloMaui
para criar o aplicativo. Em seguida, defina a propriedade MSBuild PublishAot
para true
no arquivo de projeto.
<PropertyGroup>
<PublishAot>true</PublishAot>
</PropertyGroup>
Quando você define a propriedade necessária e executa dotnet publish
como mostrado no exemplo a seguir, o aplicativo será implantado usando AOT nativo.
dotnet publish -f net8.0-ios -c Release -r ios-arm64 /t:Run
Limitações
Nem todos os recursos do iOS são compatíveis com a AOT nativa. Da mesma forma, nem todas as bibliotecas comumente usadas no iOS são compatíveis com o NativeAOT. Além das limitações existentes da implantação de AOT nativo, a lista a seguir mostra algumas das outras limitações ao direcionar plataformas semelhantes ao iOS:
- O uso da AOT nativa só é habilitado durante a implantação do aplicativo (
dotnet publish
). - A depuração de código gerenciado só é suportada com Mono.
- A compatibilidade com a estrutura .NET MAUI é limitada.
Compilação AOT para aplicativos Android
Para diminuir o tamanho do aplicativo, os aplicativos .NET e .NET MAUI destinados ao Android usam modo de compilação com perfil antecipação do tempo (AOT) quando são criados no modo de versão. A compilação AOT com perfil afeta menos métodos do que a compilação AOT regular. O .NET 8 apresenta a propriedade <AndroidStripILAfterAOT>
que permite que você opte por mais compilação AOT para aplicativos Android para diminuir ainda mais o tamanho do aplicativo.
<PropertyGroup>
<AndroidStripILAfterAOT>true</AndroidStripILAfterAOT>
</PropertyGroup>
Por padrão, a configuração AndroidStripILAfterAOT
como true
substitui a configuração AndroidEnableProfiledAot
padrão, permitindo que (quase) todos os métodos que foram compilados pela AOT sejam cortados. Pode também usar o AOT com perfil e a remoção de IL juntos, definindo explicitamente ambas as propriedades como true
:
<PropertyGroup>
<AndroidStripILAfterAOT>true</AndroidStripILAfterAOT>
<AndroidEnableProfiledAot>true</AndroidEnableProfiledAot>
</PropertyGroup>
Aplicações Windows construídas em várias plataformas
Quando você cria aplicativos destinados ao Windows em plataformas que não são do Windows, o executável resultante agora é atualizado com quaisquer recursos Win32 especificados — por exemplo, ícone de aplicativo, manifesto, informações de versão.
Anteriormente, os aplicativos tinham que ser criados no Windows para ter esses recursos. Corrigir essa lacuna no suporte de construção cruzada tem sido uma solicitação popular, pois era um ponto problemático significativo que afetava a complexidade da infraestrutura e o uso de recursos.