O que há de novo no .NET 9
Saiba mais sobre os novos recursos do .NET 9 e encontre links para documentação adicional.
O .NET 9, o sucessor do .NET 8, tem um foco especial em aplicativos nativos da nuvem e desempenho. Ele será suportado por 18 meses como uma versão de suporte de prazo padrão (STS). Você pode baixar o .NET 9 aqui.
Novo no .NET 9, a equipe de engenharia publica atualizações de visualização do .NET 9 nas Discussões do GitHub. Esse é um ótimo lugar para fazer perguntas e fornecer feedback sobre o lançamento.
Este artigo foi atualizado para o .NET 9 Preview 2. As seções a seguir descrevem as atualizações para as principais bibliotecas do .NET no .NET 9.
Tempo de execução do .NET
Serialização
No System.Text.Json, o .NET 9 tem novas opções para serializar JSON e um novo singleton que facilita a serialização usando padrões da Web.
Opções de recuo
JsonSerializerOptions inclui novas propriedades que permitem personalizar o caractere de recuo e o tamanho de recuo do JSON escrito.
var options = new JsonSerializerOptions
{
WriteIndented = true,
IndentCharacter = '\t',
IndentSize = 2,
};
string json = JsonSerializer.Serialize(
new { Value = 1 },
options
);
Console.WriteLine(json);
//{
// "Value": 1
//}
Opções padrão da Web
Se você quiser serializar com as opções padrão que o ASP.NET Core usa para aplicativos Web, use o novo JsonSerializerOptions.Web singleton.
string webJson = JsonSerializer.Serialize(
new { SomeValue = 42 },
JsonSerializerOptions.Web // Defaults to camelCase naming policy.
);
Console.WriteLine(webJson);
// {"someValue":42}
LINQ
Novos métodos CountBy e AggregateBy foram introduzidos. Esses métodos permitem agregar estado por chave sem a necessidade de alocar agrupamentos intermediários via GroupBy.
CountBy permite-lhe calcular rapidamente a frequência de cada chave. O exemplo a seguir localiza a palavra que ocorre com mais freqüência em uma cadeia de caracteres de texto.
string sourceText = """
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Sed non risus. Suspendisse lectus tortor, dignissim sit amet,
adipiscing nec, ultricies sed, dolor. Cras elementum ultrices amet diam.
""";
// Find the most frequent word in the text.
KeyValuePair<string, int> mostFrequentWord = sourceText
.Split(new char[] { ' ', '.', ',' }, StringSplitOptions.RemoveEmptyEntries)
.Select(word => word.ToLowerInvariant())
.CountBy(word => word)
.MaxBy(pair => pair.Value);
Console.WriteLine(mostFrequentWord.Key); // amet
AggregateBy Permite implementar fluxos de trabalho de uso mais geral. O exemplo a seguir mostra como você pode calcular pontuações associadas a uma determinada chave.
(string id, int score)[] data =
[
("0", 42),
("1", 5),
("2", 4),
("1", 10),
("0", 25),
];
var aggregatedData =
data.AggregateBy(
keySelector: entry => entry.id,
seed: 0,
(totalScore, curr) => totalScore + curr.score
);
foreach (var item in aggregatedData)
{
Console.WriteLine(item);
}
//(0, 67)
//(1, 15)
//(2, 4)
Index<TSource>(IEnumerable<TSource>) torna possível extrair rapidamente o índice implícito de um enumerável. Agora você pode escrever código como o trecho a seguir para indexar automaticamente itens em uma coleção.
IEnumerable<string> lines2 = File.ReadAllLines("output.txt");
foreach ((int index, string line) in lines2.Index())
{
Console.WriteLine($"Line number: {index + 1}, Line: {line}");
}
Coleções
O PriorityQueue<TElement,TPriority> tipo de coleção no System.Collections.Generic namespace inclui um novo Remove(TElement, TElement, TPriority, IEqualityComparer<TElement>) método que você pode usar para atualizar a prioridade de um item na fila.
Método PriorityQueue.Remove()
O .NET 6 introduziu a PriorityQueue<TElement,TPriority> coleção, que fornece uma implementação simples e rápida de heap de matriz. Um problema com heaps de matriz em geral é que eles não suportam atualizações prioritárias, o que os torna proibitivos para uso em algoritmos, como variações do algoritmo de Dijkstra.
Embora não seja possível implementar atualizações de prioridade eficientes de $O(\log n)$ na coleção existente, o novo PriorityQueue<TElement,TPriority>.Remove(TElement, TElement, TPriority, IEqualityComparer<TElement>) método torna possível emular atualizações prioritárias (embora no momento $O(n)$):
public static void UpdatePriority<TElement, TPriority>(
this PriorityQueue<TElement, TPriority> queue,
TElement element,
TPriority priority
)
{
// Scan the heap for entries matching the current element.
queue.Remove(element, out _, out _);
// Re-insert the entry with the new priority.
queue.Enqueue(element, priority);
}
Esse método desbloqueia usuários que desejam implementar algoritmos gráficos em contextos onde o desempenho assintótico não é um bloqueador. (Tais contextos incluem educação e prototipagem.) Por exemplo, aqui está uma implementação de brinquedo do algoritmo de Dijkstra que usa a nova API.
Criptografia
Para criptografia, o CryptographicOperations .NET 9 adiciona um novo método de hash one-shot no tipo. Ele também adiciona novas classes que usam o algoritmo KMAC.
Método CryptographicOperations.HashData()
O .NET inclui várias implementações estáticas "one-shot" de funções hash e funções relacionadas. Essas APIs incluem SHA256.HashData e HMACSHA256.HashData. As APIs one-shot são preferíveis de usar porque podem fornecer o melhor desempenho possível e reduzir ou eliminar alocações.
Se um desenvolvedor quiser fornecer uma API que ofereça suporte a hash em que o chamador define qual algoritmo de hash usar, isso geralmente é feito aceitando um HashAlgorithmName argumento. No entanto, usar esse padrão com APIs one-shot exigiria alternar todos os possíveis HashAlgorithmName e, em seguida, usar o método apropriado. Para resolver esse problema, o .NET 9 apresenta a CryptographicOperations.HashData API. Essa API permite que você produza um hash ou HMAC sobre uma entrada como um one-shot onde o algoritmo usado é determinado por um HashAlgorithmNamearquivo .
static void HashAndProcessData(HashAlgorithmName hashAlgorithmName, byte[] data)
{
byte[] hash = CryptographicOperations.HashData(hashAlgorithmName, data);
ProcessHash(hash);
}
Algoritmo KMAC
O .NET 9 fornece o algoritmo KMAC conforme especificado pelo NIST SP-800-185. KECCAK Message Authentication Code (KMAC) é uma função pseudoaleatória e função hash chaveada baseada em KECCAK.
As novas classes a seguir usam o algoritmo KMAC. Use instâncias para acumular dados para produzir um MAC ou use o método estático HashData
para uma captura única em uma única entrada.
O KMAC está disponível no Linux com OpenSSL 3.0 ou posterior e no Windows 11 Build 26016 ou posterior. Você pode usar a propriedade static IsSupported
para determinar se a plataforma suporta o algoritmo desejado.
if (Kmac128.IsSupported)
{
byte[] key = GetKmacKey();
byte[] input = GetInputToMac();
byte[] mac = Kmac128.HashData(key, input, outputLength: 32);
}
else
{
// Handle scenario where KMAC isn't available.
}
Reflexão
Nas versões .NET Core e .NET 5-8, o suporte para criar um assembly e emitir metadados de reflexão para tipos criados dinamicamente era limitado a um .AssemblyBuilder A falta de suporte para salvar um assembly era muitas vezes um bloqueador para clientes que migravam do .NET Framework para o .NET. O .NET 9 adiciona APIs públicas para AssemblyBuilder salvar um assembly emitido.
A nova implementação persistente AssemblyBuilder é independente de tempo de execução e plataforma. Para criar uma instância persistente AssemblyBuilder
, use a nova AssemblyBuilder.DefinePersistedAssembly API. A API existente AssemblyBuilder.DefineDynamicAssembly aceita o nome do assembly e atributos personalizados opcionais. Para usar a nova API, passe o assembly principal, System.Private.CoreLib
que é usado para referenciar tipos de tempo de execução básicos. Não há opção para AssemblyBuilderAccess. E, por enquanto, a implementação persistente AssemblyBuilder
suporta apenas salvar, não executar. Depois de criar uma instância do persistente, AssemblyBuilder
as etapas subsequentes para definir um módulo, tipo, método ou enum, escrever IL e todos os outros usos permanecem inalterados. Isso significa que você pode usar o código existente System.Reflection.Emit como está para salvar o assembly. O código seguinte mostra um exemplo.
public void CreateAndSaveAssembly(string assemblyPath)
{
AssemblyBuilder ab = AssemblyBuilder.DefinePersistedAssembly(
new AssemblyName("MyAssembly"),
typeof(object).Assembly
);
TypeBuilder tb = ab.DefineDynamicModule("MyModule")
.DefineType("MyType", TypeAttributes.Public | TypeAttributes.Class);
MethodBuilder mb = tb.DefineMethod(
"SumMethod",
MethodAttributes.Public | MethodAttributes.Static,
typeof(int), [typeof(int), typeof(int)]
);
ILGenerator il = mb.GetILGenerator();
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldarg_1);
il.Emit(OpCodes.Add);
il.Emit(OpCodes.Ret);
tb.CreateType();
ab.Save(assemblyPath); // or could save to a Stream
}
public void UseAssembly(string assemblyPath)
{
Assembly assembly = Assembly.LoadFrom(assemblyPath);
Type type = assembly.GetType("MyType");
MethodInfo method = type.GetMethod("SumMethod");
Console.WriteLine(method.Invoke(null, [5, 10]));
}
Desempenho
O .NET 9 inclui aprimoramentos para o compilador JIT de 64 bits com o objetivo de melhorar o desempenho do aplicativo. Esses aprimoramentos do compilador incluem:
- Melhor geração de código para loops.
- Mais inlining de método para AOT nativo.
- Verificações de tipo mais rápidas.
A vetorização Arm64 é outro novo recurso do tempo de execução.
Otimizações de loop
Melhorar a geração de código para loops é uma prioridade para o .NET 9, e o compilador de 64 bits apresenta uma nova otimização chamada alargamento da variável de indução (IV).
Um IV é uma variável cujo valor muda à medida que o loop de contenção itera. No loop seguinte for
, i
é um IV: for (int i = 0; i < 10; i++)
. Se o compilador puder analisar como o valor de um IV evolui ao longo das iterações de seu loop, ele poderá produzir código com mais desempenho para expressões relacionadas.
Considere o seguinte exemplo que itera através de uma matriz:
static int Sum(int[] arr)
{
int sum = 0;
for (int i = 0; i < arr.Length; i++)
{
sum += arr[i];
}
return sum;
}
A variável de índice, i
, tem 4 bytes de tamanho. No nível de assembly, os registradores de 64 bits são normalmente usados para manter índices de matriz em x64 e, em versões anteriores do .NET, o compilador gerou código que se estendeu i
a zero para 8 bytes para o acesso à matriz, mas continuou a ser tratado i
como um inteiro de 4 bytes em outro lugar. No entanto, estender i
para 8 bytes requer uma instrução adicional em x64. Com o alargamento IV, o compilador JIT de 64 bits agora aumenta i
para 8 bytes em todo o loop, omitindo a extensão zero. Looping sobre matrizes é muito comum, e os benefícios desta remoção de instrução rapidamente se somam.
Melhorias no inlining para AOT nativo
Um dos . Os objetivos da NET para o inliner do compilador JIT de 64 bits é remover o maior número possível de restrições que impedem que um método seja embutido. O .NET 9 permite a inserção de acessos a estáticas locais de thread no Windows x64, Linux x64 e Linux Arm64.
Para static
os membros da classe, existe exatamente uma instância do membro em todas as instâncias da classe, que "compartilham" o membro. Se o valor de um static
membro for exclusivo para cada thread, tornar esse valor thread-local pode melhorar o desempenho, pois elimina a necessidade de uma primitiva de simultaneidade para acessar com segurança o static
membro a partir de seu thread de contenção.
Anteriormente, os acessos a estáticas locais de thread em programas compilados por AOT nativo exigiam que o compilador JIT de 64 bits emitisse uma chamada para o tempo de execução para obter o endereço base do armazenamento local de thread. Agora, o compilador pode inserir essas chamadas, resultando em muito menos instruções para acessar esses dados.
Melhorias no PGO: Verificações de tipo e moldes
O .NET 8 habilitou a otimização guiada por perfil dinâmico (PGO) por padrão. NET 9 expande a implementação PGO do compilador JIT de 64 bits para criar perfis de mais padrões de código. Quando a compilação hierárquica está habilitada, o compilador JIT de 64 bits já insere instrumentação em seu programa para criar o perfil de seu comportamento. Quando recompila com otimizações, o compilador aproveita o perfil que construiu em tempo de execução para tomar decisões específicas para a execução atual do seu programa. No .NET 9, o compilador JIT de 64 bits usa dados PGO para melhorar o desempenho de verificações de tipo.
Determinar o tipo de um objeto requer uma chamada para o tempo de execução, que vem com uma penalidade de desempenho. Quando o tipo de um objeto precisa ser verificado, o compilador JIT de 64 bits emite essa chamada por uma questão de correção (os compiladores geralmente não podem descartar quaisquer possibilidades, mesmo que pareçam improváveis). No entanto, se os dados PGO sugerirem que um objeto provavelmente será um tipo específico, o compilador JIT de 64 bits agora emite um caminho rápido que verifica esse tipo de forma barata e recorre ao caminho lento de chamar para o tempo de execução somente se necessário.
Vetorização Arm64 em bibliotecas .NET
Uma nova EncodeToUtf8
implementação aproveita a capacidade do compilador JIT de 64 bits de emitir instruções de carregamento/armazenamento de vários registros no Arm64. Esse comportamento permite que os programas processem partes maiores de dados com menos instruções. Os aplicativos .NET em vários domínios devem ver melhorias na taxa de transferência no hardware Arm64 que oferece suporte a esses recursos. Alguns índices de referência reduzem o seu tempo de execução em mais de metade.
SDK do .NET
Teste de unidades
Esta seção descreve as atualizações para o teste de unidade no .NET 9: executando testes em paralelo e saída de teste do registrador de terminal.
Executar testes em paralelo
No .NET 9, dotnet test
é mais totalmente integrado com o MSBuild. Como o MSBuild oferece suporte à criação em paralelo, você pode executar testes para o mesmo projeto em diferentes estruturas de destino em paralelo. Por padrão, o MSBuild limita o número de processos paralelos ao número de processadores no computador. Você também pode definir seu próprio limite usando a opção -maxcpucount . Se você quiser desativar o paralelismo, defina a TestTfmsInParallel
propriedade MSBuild como false
.
Visor de teste do registrador de terminal
O relatório de resultados de teste para dotnet test
agora é suportado diretamente no registrador de terminal MSBuild. Você obtém relatórios de teste com mais recursos enquanto os testes estão em execução (exibe o nome do teste em execução) e depois que os testes são concluídos (quaisquer erros de teste são renderizados de uma maneira melhor).
Para obter mais informações sobre o registrador de terminal, consulte opções de compilação dotnet.
Roll-forward da ferramenta .NET
As ferramentas .NET são aplicativos dependentes da estrutura que você pode instalar globalmente ou localmente e, em seguida, executar usando o SDK do .NET e os tempos de execução do .NET instalados. Essas ferramentas, como todos os aplicativos .NET, destinam-se a uma versão principal específica do .NET. Por padrão, os aplicativos não são executados em versões mais recentes do .NET. Os autores de ferramentas puderam optar por executar suas ferramentas em versões mais recentes do tempo de execução do .NET definindo a RollForward
propriedade MSBuild. No entanto, nem todas as ferramentas o fazem.
Uma nova opção para dotnet tool install
permite que os usuários decidam como as ferramentas .NET devem ser executadas. Quando você instala uma ferramenta via dotnet tool install
, ou quando executa a ferramenta via dotnet tool run <toolname>
, você pode especificar um novo sinalizador chamado --allow-roll-forward
. Esta opção configura a ferramenta com o modo Major
roll-forward. Esse modo permite que a ferramenta seja executada em uma versão principal mais recente do .NET se a versão correspondente do .NET não estiver disponível. Esse recurso ajuda os primeiros usuários a usar ferramentas .NET sem que os autores de ferramentas precisem alterar qualquer código.