Novidades do .NET 6 - Preview 2
O .NET 6 Preview 2 chegou pouco mais de duas semanas depois do Preview 1, com novidades concentradas principalmente no .NET MAUI. Além disso, a versão trouxe novas APIs, melhorias de performance, um build inicial do MAUI e o primeiro build para Apple Silicon, que não chegou no Preview 1.
A Microsoft também confirmou que lançaria previews mensais até novembro de 2021, mês do lançamento oficial do .NET 6. Por isso, se você ainda não leu o nosso post sobre as novidades do .NET 6 Preview 1, vale a pena começar por ele antes de seguir aqui.
O que é o .NET 6 Preview 2
O .NET 6 Preview 2 é a segunda versão de testes da plataforma que unificaria desenvolvimento web, desktop, mobile e nuvem em um único SDK. A Microsoft publicou o anúncio em março de 2021, no blog oficial do .NET, com a lista completa de mudanças.
A Microsoft planejou o .NET 6 como uma versão LTS (Long Term Support), com três anos de suporte a partir do lançamento. Assim, cada preview ajudava a comunidade a testar cedo o que rodaria em produção por muito tempo.
O cronograma também ficou mais claro nesse anúncio. Segundo a equipe, as funcionalidades deveriam estar completas por volta de julho de 2021, e os meses seguintes ficariam dedicados à qualidade.
Melhorias de performance
Performance sempre foi um foco do .NET, e cada versão nova traz avanços nesse ponto. No entanto, no .NET 6 o objetivo ficou ainda maior.
A equipe queria garantir que não só as aplicações ficassem otimizadas, mas também o nosso dia a dia como desenvolvedores. Em outras palavras, o foco era o chamado inner loop: alterar o código, compilar, testar e repetir.
Por isso, houve um esforço grande para acelerar o startup das aplicações, o dotnet cli e o MSBuild. O gráfico abaixo compara os tempos de build do .NET 5 com os primeiros resultados do .NET 6.
O que mudou no inner loop do .NET 6 Preview 2
Segundo o anúncio oficial, várias correções contribuíram para esses números. Por exemplo, a equipe eliminou compilações JIT inesperadas nos comandos do dotnet e removeu o custo de logging quando ele estava desabilitado.
Além disso, a equipe otimizou os padrões de globbing do MSBuild. O host do .NET também passou a acessar menos arquivos, o que reduz o número de verificações disparadas pelos antivírus.
Outra mudança importante foi a conversão do compilador Razor para um source generator do Roslyn, assunto que veremos mais adiante. Com isso, a etapa extra de compilação das views deixou de existir.
A equipe também citou o Hot Reload como parte dessa estratégia. A ideia é permitir que muitos tipos de edição sejam aplicados com a aplicação em execução, sem reiniciar o processo.
Melhor experiência de desenvolvimento
Uma das partes mais aguardadas do .NET 6 é o suporte ao desenvolvimento mobile, que até então era entregue em um produto separado, o Xamarin. Durante o seu crescimento, o Xamarin ficou cada vez mais parecido com o .NET e, inclusive, inspirou boas ideias nas versões mais novas da plataforma.
Nos últimos anos, a Microsoft trabalhou para trazer o Mono para dentro do .NET. Isso faz sentido, pois os dois têm muitas semelhanças, embora ainda precisem de targets diferentes.
No .NET 5, o Blazor WebAssembly já tinha seguido esse caminho. Agora, o Xamarin também seria incorporado, o que aumenta a quantidade de código compartilhado entre os projetos .NET.
Essa é a experiência que a equipe quer entregar com o .NET 6: não apenas performance, mas conforto no desenvolvimento, um mesmo ambiente e mais reaproveitamento de código.
O .NET MAUI no .NET 6 Preview 2
O .NET 6 Preview 2 adicionou o suporte ao projeto único do MAUI, que tinha sido apresentado no Preview 1. Dessa forma, um mesmo projeto passa a manter Android, iOS, desktop e o que mais vier pela frente.
Também chegou o suporte ao Mac Catalyst, que permite criar aplicações compatíveis com o ecossistema Apple (Mac, iPad e iPhone). Para isso, basta adicionar o target correspondente ao projeto.
<TargetFrameworks>net6.0-android;net6.0-ios</TargetFrameworks>
<TargetFrameworks Condition=" '$(OS)' != 'Windows_NT' ">$(TargetFrameworks);net6.0-maccatalyst</TargetFrameworks>
Repare que o target net6.0-maccatalyst só entra quando o sistema operacional não é o Windows. Afinal, o build para Mac Catalyst depende das ferramentas da Apple.
A nova estrutura do projeto ficou bem simples, e agora é possível visualizar todas as plataformas na mesma solução de forma clara. Por outro lado, o suporte ao Windows dependia do WinUI 3, que também estava em preview na época.
Compartilhamento de assets no .NET 6 Preview 2
Fontes, imagens, ícones e outros assets agora podem ser compartilhados entre as plataformas, em uma pasta separada. Mesmo assim, cada plataforma continua tratando esses arquivos de forma nativa.
Você pode referenciar cada arquivo individualmente no seu *.csproj, assim:
<ItemGroup>
<SharedImage Include="appicon.svg" ForegroundFile="appiconfg.svg" IsAppIcon="true" />
<SharedFont Include="Resources\Fonts\ionicons.ttf" />
</ItemGroup>
Também é possível usar curingas para incluir pastas inteiras de uma vez:
<ItemGroup>
<SharedImage Include="appicon.svg" ForegroundFile="appiconfg.svg" IsAppIcon="true" />
<SharedImage Include="Resources\Images*" />
<SharedFont Include="Resources\Fonts*" />
</ItemGroup>
Na prática, isso elimina a cópia manual de imagens para cada projeto de plataforma, uma tarefa comum no Xamarin.Forms.
HostBuilder no MAUI
Quem trabalha com ASP.NET já conhece o HostBuilder, usado para fazer o bootstrap da aplicação. No MAUI, esse modelo também passa a existir, o que torna a injeção de dependência (DI) bem mais simples.
Segundo a Microsoft, o novo padrão oferece um ponto único e consistente para que autores de bibliotecas e fornecedores de controles se integrem ao .NET MAUI. Além disso, a nova interface IWindow vai permitir, futuramente, a gestão de múltiplas janelas.
public class Application : MauiApp
{
public override IAppHostBuilder CreateBuilder() =>
base.CreateBuilder()
.RegisterCompatibilityRenderers()
.ConfigureServices((ctx, services) =>
{
services.AddTransient<MainPage>();
services.AddTransient<IWindow, MainWindow>();
})
.ConfigureFonts((hostingContext, fonts) =>
{
fonts.AddFont("ionicons.ttf", "IonIcons");
});
public override IWindow CreateWindow(IActivationState state)
{
Microsoft.Maui.Controls.Compatibility.Forms.Init(state);
return Services.GetService<IWindow>();
}
}
No exemplo, o método ConfigureServices registra a MainPage e a MainWindow no container. Em seguida, o CreateWindow resolve a janela a partir dos serviços configurados.
Vale lembrar que essa API era experimental. Portanto, os nomes de tipos e métodos mudaram nas versões seguintes até chegar ao modelo final do MAUI.
Novos controles do .NET 6 Preview 2
Nesta versão chegaram os primeiros controles e propriedades que implementam a nova arquitetura de handlers do MAUI. Eles incluem implementações parciais de Button, Label, Entry, Slider e Switch.
O repositório no GitHub já estava aberto para contribuições, então a expectativa era de boas novidades nos meses seguintes. Se quiser colocar a mão na massa, temos também um hands-on com o MAUI nesta versão.
O exemplo publicado junto com o .NET 6 Preview 2 usa esses controles, e o mesmo projeto roda no macOS, no iOS e no Android. O resultado é este:
macOS
iOS
Android
SDKs do Android e do iOS
Os SDKs do Android e do iOS também receberam melhorias. Por exemplo, os projetos Android passaram a usar as bibliotecas AndroidX por padrão.
No iOS, chegou o Remote iOS Simulator, que permite a quem desenvolve no Windows visualizar o simulador rodando em um Mac. Além disso, é possível usar o Mac como máquina de build remoto e compilar com AOT para dispositivos físicos.
Novas APIs do .NET 6 Preview 2
Além do MAUI, as bibliotecas base ganharam APIs novas e aguardadas. A seguir, veja as principais mudanças do .NET 6 Preview 2 para o código do dia a dia.
System.Text.Json e o ReferenceHandler.IgnoreCycles
O JsonSerializer do System.Text.Json agora consegue ignorar ciclos de referência. A opção ReferenceHandler.IgnoreCycles tem comportamento parecido com o ReferenceLoopHandling.Ignore do Newtonsoft.Json.
No entanto, existe uma diferença importante. O System.Text.Json substitui o loop de referência pelo token null, enquanto o Newtonsoft.Json simplesmente ignora a referência do objeto.
No exemplo abaixo, a propriedade Next sai como null, porque ela aponta para o próprio objeto e geraria um ciclo.
class Node
{
public string Description { get; set; }
public object Next { get; set; }
}
void Test()
{
var node = new Node { Description = "Node 1" };
node.Next = node;
var opts = new JsonSerializerOptions { ReferenceHandler = ReferenceHandler.IgnoreCycles };
string json = JsonSerializer.Serialize(node, opts);
Console.WriteLine(json); // Imprime {"Description":"Node 1","Next":null}
}
Segundo a documentação, essa abordagem reduz o tamanho do payload e gera um JSON que outros serializadores entendem. Por outro lado, há perda silenciosa de dados, e o JSON não consegue reconstruir o objeto original.
Se você precisa manter as referências, a alternativa é o ReferenceHandler.Preserve. Nesse caso, o serializador escreve metadados como $id e $ref no JSON.
PriorityQueue
O PriorityQueue<TElement, TPriority> é uma nova coleção do namespace System.Collections.Generic. Além do elemento, cada item guarda a sua prioridade.
Ao desenfileirar, o PriorityQueue devolve o elemento com o menor valor de prioridade. Ou seja, ele lembra o Queue<T>, mas a ordem de saída respeita a prioridade de cada item.
// cria uma fila de prioridade de strings com prioridades inteiras
var pq = new PriorityQueue<string, int>();
// enfileira os elementos com suas prioridades
pq.Enqueue("A", 3);
pq.Enqueue("B", 1);
pq.Enqueue("C", 2);
pq.Enqueue("D", 3);
pq.Dequeue(); // retorna "B"
pq.Dequeue(); // retorna "C"
pq.Dequeue(); // "A" ou "D", a ordem entre prioridades iguais não é garantida
Internamente, a coleção usa um min-heap quaternário baseado em array. Por isso, ela não garante a ordem de chegada entre elementos com a mesma prioridade, como mostra o último Dequeue.
Se quiser se aprofundar, temos um artigo dedicado ao uso do PriorityQueue no C#.
Melhorias na formatação de números
Os tipos numéricos também receberam melhorias de padronização, especialmente nos métodos ToString e TryFormat. Agora, eles aceitam especificadores de precisão acima de 99 casas decimais.
Por exemplo, uma chamada como 32.ToString("C100") passa a formatar o valor com 100 casas decimais em vez de falhar. Além disso, o parse passou a tratar corretamente zeros à direita.
Nullable annotations no SignalR
O pacote do cliente do SignalR para ASP.NET Core recebeu anotações de nulabilidade. Assim, quando você habilita os nullable reference types, o compilador C# avisa sobre o tratamento de nulos ao usar o cliente.
O servidor do SignalR já tinha recebido essas anotações no ASP.NET Core 5.0. Agora, a cobertura chega ao cliente também.
Os nullable reference types foram um grande foco desde o .NET Core 3.x. Para ativar o recurso, basta adicionar <Nullable>enable</Nullable> ao seu projeto.
Runtime do .NET 6 Preview 2
O runtime também evoluiu nesta versão. A mudança mais visível está na forma como os assemblies do framework são pré-compilados.
Compilação com crossgen2 no .NET 6 Preview 2
O crossgen2 agora compila todos os assemblies das bibliotecas base, para todos os sistemas operacionais e arquiteturas suportados. O principal ganho dessa mudança é uma pequena economia no tamanho do runtime instalado.
Size [MB] FullName
--------- --------
64.22 C:\Program Files\dotnet\shared\Microsoft.NETCore.App\5.0.3
63.31 C:\Program Files\dotnet\shared\Microsoft.NETCore.App\6.0.0-preview.1.21102.12
63.00 C:\Program Files\dotnet\shared\Microsoft.NETCore.App\6.0.0-preview.2.21118.6
Como a tabela mostra, o runtime caiu de 64,22 MB no .NET 5.0.3 para 63,00 MB no .NET 6 Preview 2.
PGO, JIT e novas plataformas
O anúncio também citou avanços em Profile Guided Optimization (PGO), como otimizações em chamadas virtuais e instrumentação mais eficiente na compilação em camadas. Além disso, o JIT ganhou alinhamento de loops e o intrínseco MultiplyHigh para Arm64.
Em relação a plataformas, o .NET 6 prometia suporte a Android, iOS, macOS em x64 e Apple Silicon e Windows Arm64 para desktop. Por fim, as imagens de container passaram a usar o Debian 11, conhecido como "bullseye".
Para testar o .NET 6 Preview 2, a Microsoft indicava o Visual Studio 16.9 e o Visual Studio for Mac 8.9.
Novidades do ASP.NET no .NET 6 Preview 2
O ASP.NET Core também recebeu mudanças relevantes no .NET 6 Preview 2, com destaque para o compilador Razor e para o Blazor.
Compilador Razor com source generators
O compilador do Razor passou a usar source generators, recurso do C# que inspeciona o código durante a compilação. Com ele, é possível gerar arquivos adicionais que são compilados junto com o restante do projeto.
Segundo a equipe do ASP.NET, essa abordagem simplifica o compilador e acelera bastante o build. O gráfico abaixo mostra o ganho nos templates de Blazor Server e MVC.
Argumentos customizados em eventos do Blazor
Agora o Blazor permite criar eventos customizados que enviam dados arbitrários para os handlers em .NET. O primeiro passo é declarar o evento e a classe de argumentos em C#.
[EventHandler("oncustompaste", typeof(CustomPasteEventArgs), enableStopPropagation: true, enablePreventDefault: true)]
public static class EventHandlers
{
// Esta classe estática não precisa de membros. Ela só serve para abrigar
// os atributos [EventHandler] que configuram os tipos de evento no compilador
// Razor, o que afeta a saída do compilador e o autocomplete do editor.
}
public class CustomPasteEventArgs : EventArgs
{
// Os dados destas propriedades vêm de uma lógica customizada em JavaScript
public DateTime EventTimestamp { get; set; }
public string PastedData { get; set; }
}
Em seguida, você registra o evento no JavaScript com Blazor.registerCustomEventType, que associa um evento do navegador (como paste) à criação dos argumentos. Depois disso, o componente pode usar @oncustompaste como qualquer outro evento.
CSS isolation em MVC e Razor Pages
O CSS isolation, que já existia nos componentes Blazor, chegou às views MVC e às Razor Pages. Para usar, basta criar um arquivo *.cshtml.css com o mesmo nome da view.
Esse CSS fica restrito à view correspondente e entra automaticamente no bundle da aplicação.
Index.cshtml.css
h1 {
color: red;
}
Depois, referencie o bundle gerado no layout, no formato {nome do projeto}.styles.css.
_Layout.cshtml
<link rel="stylesheet" href="MyWebApp.styles.css" />
Resultado
Tipos genéricos inferidos de componentes ancestrais
Outra novidade do Blazor é o atributo [CascadingTypeParameter]. Com ele, um componente genérico repassa o seu parâmetro de tipo aos descendentes.
Por exemplo, um Grid com @typeparam TItem pode cascatear esse tipo para os componentes Column internos. Assim, você não precisa repetir o tipo em cada coluna.
Estado dos apps Blazor no .NET 6 Preview 2
Os apps Blazor podem ser pré-renderizados no servidor para carregar mais rápido. Porém, todo o estado usado durante a pré-renderização se perdia e precisava ser recriado quando a aplicação terminava de carregar.
Para resolver esse problema, o .NET 6 Preview 2 trouxe uma Tag Helper que grava o estado na própria página pré-renderizada. Na versão final do .NET 6, ela se chama <persist-component-state /> e trabalha junto com o serviço PersistentComponentState.
Com isso, as etapas caras de inicialização rodam apenas uma vez. Além disso, a interface final bate com a pré-renderizada, e o usuário não vê a tela piscar.
Para entender o contexto do Blazor, confira também a nossa visão geral do Blazor.
Conclusão
O .NET 6 Preview 2 deixou claro o rumo da plataforma: um único SDK para web, desktop e mobile, com foco em produtividade. O MAUI ganhou forma, com projeto único, assets compartilhados, HostBuilder e os primeiros handlers.
Ao mesmo tempo, as bibliotecas base ficaram mais completas com o IgnoreCycles e o PriorityQueue. Já o runtime e o ASP.NET trouxeram ganhos de build, crossgen2 e recursos novos no Blazor.
Como toda versão de preview, várias APIs mudaram antes do lançamento final. Mesmo assim, o .NET 6 Preview 2 é um bom retrato de como o .NET 6 foi construído, e muitas dessas ideias chegaram inteiras à versão LTS.
FAQ
Quando o .NET 6 Preview 2 foi lançado?
A Microsoft o anunciou em março de 2021, pouco mais de duas semanas depois do Preview 1. A versão final do .NET 6 saiu em novembro de 2021.
O .NET 6 Preview 2 já suportava Windows no MAUI?
Não. O suporte ao Windows dependia do WinUI 3, que ainda estava em preview, então o exemplo rodava em Android, iOS e macOS.
Qual a diferença entre IgnoreCycles e Preserve no System.Text.Json?
O IgnoreCycles troca a referência circular por null. Já o Preserve mantém as referências usando metadados como $id e $ref.
O PriorityQueue garante a ordem de chegada?
Não. Elementos com a mesma prioridade podem sair em qualquer ordem, porque a coleção usa um heap internamente.
Posso usar a API de HostBuilder do MAUI mostrada aqui hoje?
Não da mesma forma. A API era experimental e mudou até a versão final do MAUI, que usa o MauiApp.CreateBuilder no MauiProgram.