Novidades do SDK no .NET 11 - Preview
O SDK do .NET 11 recebeu no Preview 7 a entrega mais consequente do ciclo até agora, não pela quantidade de novidades, mas pela natureza delas. Duas capacidades que existiam atrás de flags de opt-in passaram a ser o comportamento padrão.
Isso muda o que acontece quando alguém digita dotnet no terminal, mesmo sem configurar absolutamente nada. Por isso, vale entender cada mudança antes que ela chegue à sua máquina de build.
Onde o Preview 7 se encaixa no calendário
O .NET 11 é uma versão STS, com disponibilidade geral prevista para novembro de 2026 e suporte até novembro de 2028. Assim, o Preview 7 chega na reta final do ciclo de previews.
Mudanças de padrão nesta altura tendem a chegar ao GA sem grandes recuos. É exatamente por isso que elas merecem atenção agora, e não em novembro.
O artigo sobre as novidades no SDK e ferramentas do Preview 5 mostra a fase em que esses recursos ainda dependiam de opt-in.
O CLI NativeAOT vira padrão no SDK do .NET 11
O trabalho de compilar o CLI do dotnet com NativeAOT vem sendo construído aos poucos desde os primeiros previews. A motivação é direta: eliminar o custo de inicializar o CoreCLR para comandos que não precisam dele.
No Preview 6, o caminho rápido em AOT já parseava, validava e renderizava o --help de todos os comandos internos. Contudo, ele continuava desligado por padrão, atrás da variável DOTNET_CLI_ENABLEAOT.
O Preview 7 inverte esse padrão no SDK do .NET 11 em todas as plataformas. Isso inclui macOS e Linux, onde uma trava anterior mantinha o recurso desabilitado.
Caso algum comando se comporte de forma inesperada, o opt-out continua disponível. A variável aceita valores falsos como false, 0, no ou off.
# Volta ao caminho gerenciado apenas para um comando
DOTNET_CLI_ENABLEAOT=false dotnet --info
Os números por trás da decisão
A justificativa para promover o AOT a padrão está em medições concretas. Por exemplo, execuções pareadas de dotnet tool list caíram de 378 ms para 68 ms, uma redução de 82%.
Em outras palavras, o comando ficou cerca de 5,5 vezes mais rápido. Comandos de despacho de ferramentas mostraram o mesmo padrão.
Os comandos dotnet dev-certs https e dotnet ef saíram da faixa de 700 ms para algo entre 200 ms e 220 ms. Na prática, isso representa um ganho de 3,2 a 3,5 vezes.
A origem do ganho é a mesma nos dois casos. O caminho AOT elimina uma segunda inicialização do CoreCLR que existia apenas para hospedar o fallback gerenciado do CLI.
O que roda em AOT e o que ainda cai no fallback
O caminho rápido não cobre tudo, e a fronteira é simples. Comandos que dependem de MSBuild ou NuGet em processo continuam no CLI gerenciado.
Do lado AOT ficam os comandos internos leves, como dotnet --version, dotnet --info e dotnet sdk check. Além disso, entram dotnet sln list, sln migrate e sln remove.
Também rodam em AOT dotnet tool list --local, tool run, tool uninstall --local e tool search. A resolução e a invocação de comandos externos saem desse mesmo caminho.
Isso inclui ferramentas globais, ferramentas locais, comandos no PATH e comandos de app base. Por outro lado, build, run, test, pack, publish e sln add continuam no CLI gerenciado.
A execução de apps baseados em arquivo também segue pelo caminho gerenciado, com troca transparente para quem digita o comando.
Ajustes que acompanharam a virada de padrão
Promover algo a padrão exige paridade de comportamento, e boa parte do trabalho do Preview 7 foi exatamente isso. O dotnet --info agora emite versão e lista de workloads, além da versão do MSBuild, direto do binário AOT.
Essa saída fica idêntica, linha a linha, à do CLI gerenciado. Da mesma forma, o ponto de entrada AOT passou a servir também a experiência de primeira execução.
O muxer AOT ganhou a mesma lógica de resolução de diretório versionado do SDK que o muxer gerenciado já tinha. Isso importa em máquinas com vários SDKs instalados lado a lado.
Houve ainda correções de infraestrutura no SDK do .NET 11, como a resolução do hostfxr em distribuições musl sem HOSTFXR_PATH definida.
MSBuild server ligado por padrão no SDK do .NET 11
O MSBuild server mantém um processo de trabalho aquecido entre invocações do CLI. Com isso, chamadas consecutivas de dotnet build, dotnet test e dotnet run deixam de pagar o custo de inicialização do MSBuild.
O recurso existe desde o .NET 7, mas ficou anos desabilitado por padrão. No Preview 6, o CLI deixou de sobrescrever incondicionalmente a variável MSBUILDUSESERVER.
Agora o SDK do .NET 11 completa o movimento e liga o servidor por padrão. O opt-out honra duas variáveis de ambiente, conforme a documentação de novidades do SDK.
# Qualquer uma das duas mantém o comportamento clássico
export DOTNET_CLI_USE_MSBUILD_SERVER=false
export MSBUILDUSESERVER=0
Um detalhe importante é a autoridade dessa configuração. O valor DOTNET_CLI_USE_MSBUILD_SERVER=false agora é definitivo, porque encaminha MSBUILDUSESERVER=0 ao MSBuild.
Assim, arquivos de resposta, a variável MSBUILDFORCEMULTITHREADED=1 ou a opção /mt não conseguem reabilitar o servidor silenciosamente. Isso importa em integração contínua, onde cada build sobe um ambiente descartável e o servidor tende a ser contraproducente.
Avaliação parcial de projetos
Junto com a virada do servidor, os caminhos quentes do CLI adotaram a nova API de avaliação parcial do MSBuild. Por exemplo, dotnet sln add e dotnet reference list interrompem a avaliação após a passagem que produz o dado necessário.
O mesmo vale para consultas a propriedades de release. Portanto, repositórios grandes, com muitas propriedades e importações, tendem a sentir mais a diferença.
dotnet test no SDK do .NET 11
Sob o Microsoft.Testing.Platform, o dotnet test passou a aceitar duas opções que valem para a execução inteira. Ou seja, elas não se aplicam a cada aplicação de teste isoladamente.
A distinção depende da posição do argumento. Antes do --, as opções valem para o run completo, enquanto depois dele continuam por aplicação de teste, mantendo o comportamento nativo do MTP.
# Aborta o run inteiro após 90 segundos, com código de saída 3
dotnet test --timeout 90s
# Encerra o run quando 5 falhas se acumularem no total, com código de saída 13
dotnet test --maximum-failed-tests 5
O --timeout aceita os sufixos ms, s e m. Além disso, o relógio só avança enquanto houver ao menos uma aplicação de teste em execução, então tempo de build ou de espera não consome o orçamento.
As duas políticas disparam cancelamento cooperativo através de um novo pipe de controle reverso. Esse pipe aparece como a capacidade de protocolo ServerControlPipeName.
Para isso, o SDK do .NET 11 passou a negociar a versão 2.4 do MTP. Dessa forma, o host consegue honrar mensagens CancelSession, finalizar artefatos e liberar recursos antes de encerrar.
Suporte a projetos Microsoft.Build.Traversal
No SDK do .NET 11, o dotnet test sobre MTP aceita um projeto Microsoft.Build.Traversal como entrada. O SDK expande grafos aninhados e elimina referências duplicadas em diamante.
Além disso, ele encaminha os metadados de Configuration e Platform de cada referência durante a avaliação e o build.
dotnet test dirs.proj
Para times que organizam suas suítes através de projetos de travessia, isso elimina a necessidade de enumerar projetos de teste um a um.
Reporters do MTP no SDK do .NET 11
Um bloco significativo do Preview 7 trata de como os resultados aparecem, e não de como os testes rodam. O suporte a comandos de log do Azure Pipelines é o mais visível.
O SDK do .NET 11 passou a negociar as versões de protocolo 1.2.0 e 1.3.0. Com isso, ele encaminha os quadros AzureDevOpsLogMessage e DisplayMessage para o console.
Assim, comandos como ##[group] e ##vso[...] emitidos pela extensão AzureDevOpsReport finalmente renderizam no log do pipeline.
O reporter de terminal também passou a exibir avisos e erros genéricos do host, como dumps de crash e resumos de retentativa.
Os diffs entre valor esperado e valor obtido chegaram às execuções com vários assemblies. Isso elimina a assimetria entre rodar um projeto e rodar a solução inteira.
O veredito de zero testes também mudou para o nível da execução. Uma rodada de solução não falha mais só porque um projeto não casou com nenhum teste, embora o diagnóstico Exit code: 8 por módulo continue disponível.
Em execuções com vários módulos, o dotnet test agora consolida arquivos TRX e de cobertura compatíveis antes do resumo final. Se o pós-processamento falhar, o comando preserva os artefatos originais e o código de saída.
Quem preferir um artefato por aplicação de teste pode usar --no-artifact-post-processing.
No SDK do .NET 11, o dotnet test também ganhou --no-logo e --no-banner para suprimir o cabeçalho.
Descoberta de testes em JSON
No SDK do .NET 11, a opção --list-tests passou a aceitar um formato de saída opcional. O padrão continua sendo text, e a alternativa nova é json.
Dessa forma, ferramentas consomem a lista de testes descobertos em vez de raspar uma saída feita para humanos.
dotnet test --list-tests json
O próprio SDK monta o JSON a partir dos dados de descoberta que já recebe pelo protocolo dotnettestcli. Consequentemente, o recurso funciona com qualquer host MTP, sem mudança do lado do host.
{
"version": "1.0",
"testContainers": [
{
"assemblyPath": "MyTests.dll",
"targetFramework": "net11.0",
"architecture": "x64",
"tests": [
{ "uid": "MyTests.CalculatorTests.Add", "displayName": "Add" }
]
}
]
}
Testes em dispositivos MAUI
O dotnet test continua absorvendo capacidades de dispositivo que o dotnet run já possuía para projetos MAUI, Android e iOS. A opção --list-devices lista os identificadores aceitos por --device, sem exigir um build prévio.
A correção mais substantiva aqui é de ordenação. Agora o dotnet test escolhe o dispositivo antes de construir, para que o identificador de runtime daquele alvo participe do build de cada target framework.
Depois de um build bem-sucedido, o comando executa DeployToDevice antes de calcular os argumentos do host de teste. Assim, os testes rodam contra o que acabou de chegar ao dispositivo, e não contra uma cópia obsoleta.
O fluxo roda inclusive com --no-build, pois um dispositivo diferente pode precisar de uma nova implantação de um build existente.
# Constrói para um dispositivo específico, implanta e roda os testes
dotnet test --project MyMauiTests.csproj --device <device-id>
Falhas de implantação agora interrompem a execução com erro. Em vez de uma falha silenciosa e confusa, você recebe uma falha explícita e cedo.
Por fim, no SDK do .NET 11 as opções -e e --environment ganharam paridade. Variáveis definidas na linha de comando atravessam todo o pipeline MTP como itens @(RuntimeEnvironmentVariable), em projetos que declaram RuntimeEnvironmentVariableSupport.
Apps baseados em arquivo no SDK do .NET 11
Os apps baseados em arquivo, introduzidos no .NET 10, agora se integram ao comando dotnet reference. O subcomando de adição insere uma diretiva #:project no arquivo C#, enquanto list e remove gerenciam essa diretiva no lugar.
dotnet new classlib -n MyLib -o MyLib
echo 'Console.WriteLine(MyLib.Class1.Greet());' > app.cs
dotnet reference add --file app.cs MyLib/MyLib.csproj
Além disso, várias arestas foram aparadas. A verificação de atualidade do RunCommand deixou de tratar um arquivo inalterado como obsoleto depois de uma atualização de propriedades.
A correção de estabilidade mais importante envolve cache. O projeto virtual desses apps deixou de sair do cache em memória entre comandos relacionados, o que causava falhas intermitentes de MSB4025.
Contêineres no SDK do .NET 11
O pipeline de publicação de contêineres passou a reconhecer CLIs nativos de cada plataforma, especificamente wslc no Windows e container no macOS. A seleção é automática, com preferência pelo runtime nativo e Docker seguido de Podman como alternativas.
Se você ainda não usa esse recurso, o artigo sobre publicar aplicações .NET como contêineres sem Dockerfile mostra a base do fluxo. A diferença agora está em quem executa a carga da imagem localmente.
Na arquitetura nova do SDK do .NET 11, cada runtime tem sua própria sonda de prontidão e seu comando de carga. Por isso, diferenças entre engines aparecem como erros claros.
Quem precisar fixar um runtime usa a propriedade LocalRegistry. Ela aceita Docker, Podman, Wslc e o novo valor MacOSContainer.
<PropertyGroup>
<PublishProfile>DefaultContainer</PublishProfile>
<LocalRegistry>Wslc</LocalRegistry>
</PropertyGroup>
Além disso, o Podman deixou de pular a criação do índice de imagem e agora produz uma lista de manifestos apropriada.
Empacotamento de ferramentas por RID
Autores que publicam ferramentas .NET compiladas com AOT ganharam um ponto de extensão. Basta implementar o target ComputeToolPackageRuntimeIdentifiersToPack para declarar quais RIDs a toolchain consegue construir.
Com essa lista, o SDK do .NET 11 orquestra um pack interno por RID. Sem uma toolchain customizada, ele usa uma matriz padrão conservadora.
Breaking changes do SDK do .NET 11 Preview 7
O Preview 7 lista quatro mudanças de comportamento que exigem atenção. As duas primeiras são as viradas de padrão já discutidas: o CLI NativeAOT e a seleção automática do runtime local de contêiner.
A terceira envolve a resolução de RIDs.
Pacotes de ferramentas .NET agora resolvem identificadores contra o grafo portátil. Assim, distribuições conhecidas apenas pelo grafo legado, como algumas variantes de BSD, passam a precisar de uma entrada portátil.
A quarta mudança é a que mais tende a aparecer em pipelines existentes. Projetos SDK agora definem BuildProjectReferences como falso quando NoBuild é verdadeiro.
Com isso, dotnet publish --no-build e dotnet pack --no-build não disparam mais um NETSDK1085 oculto. Se o seu build dependia desse comportamento, a correção é explícita.
# Restaura o comportamento anterior para um publish sem build
dotnet publish --no-build -p:BuildProjectReferences=true
Como preparar seus pipelines para o SDK do .NET 11
A pergunta que fica é o que fazer com essa informação hoje, com o GA a poucos meses de distância. A resposta depende menos das features e mais das viradas de padrão do SDK do .NET 11.
Times que mantêm imagens de build, agentes de CI ou scripts de automação deveriam testar o Preview 7 cedo. Afinal, nada precisa ser configurado para que o comportamento mude.
O ponto de maior risco prático é a combinação de --no-build com referências de projeto. Essa é a única mudança da lista que pode transformar um build verde em um artefato incorreto, sem erro visível.
Em CI, também vale decidir conscientemente sobre o MSBuild server. Em agentes descartáveis, desligar com DOTNET_CLI_USE_MSBUILD_SERVER=false costuma ser a escolha mais previsível.
Do lado do ganho, economizar 300 ms por invocação parece pouco. No entanto, multiplicado por milhares de execuções em uma frota de agentes, o número deixa de ser pequeno.
Para entender o que mudou no runtime nesse mesmo preview, o artigo sobre o runtime do .NET 11 Preview 7 complementa esta leitura. Já para o histórico do ciclo, vale rever as novidades do SDK no Preview 1.
Conclusão
O Preview 7 não é um release de features chamativas, e essa é exatamente a sua importância. Ele consolida trabalho que vinha se acumulando atrás de flags e assume o custo de mudar o comportamento padrão do SDK do .NET 11.
Para quem desenvolve no dia a dia, a diferença mais perceptível será a velocidade do terminal. Comandos de inspeção e de ferramentas ficaram várias vezes mais rápidos, e builds consecutivos deixaram de pagar a inicialização do MSBuild.
Para times de plataforma, a mensagem é outra. Vale reservar tempo para validar pipelines contra o Preview 7 agora, enquanto ainda há espaço para reportar problemas antes de novembro.
Por fim, lembre que o .NET 11 é uma versão STS. Permanecer no .NET 10 LTS é uma decisão legítima, mas ignorar o SDK do .NET 11 apenas adia o custo de aprendizado para o próximo LTS.
FAQ
O CLI NativeAOT muda o comportamento dos meus comandos?
Não deveria, porque o objetivo declarado é paridade completa de saída com o CLI gerenciado. Comandos sem cobertura em AOT caem de forma transparente no caminho gerenciado.
Como desligo o CLI NativeAOT?
Defina DOTNET_CLI_ENABLEAOT com um valor falso, como false, 0, no ou off. A variável vale para uma invocação isolada ou para toda a sessão, se exportada.
O MSBuild server ligado por padrão é bom para CI?
Geralmente não, porque o servidor se beneficia de contexto reutilizado entre invocações próximas. Em pipelines que sobem e destroem um ambiente por execução, desabilite com DOTNET_CLI_USE_MSBUILD_SERVER=false.
Qual a diferença entre passar --timeout antes ou depois do --?
Antes do --, a opção vale para o run inteiro e aborta tudo no limite. Depois do --, ela segue para cada aplicação de teste, como no comportamento nativo do MTP.
Preciso mudar meu host de teste para usar --list-tests json?
Não, porque o próprio SDK gera o JSON com os dados de descoberta do protocolo dotnettestcli. Qualquer host MTP funciona sem alteração.
A mudança em NoBuild pode quebrar meu pipeline silenciosamente?
Pode, se você dependia de --no-build para construir referências de projeto desatualizadas. Nesse caso, defina BuildProjectReferences=true explicitamente.
Vale usar o .NET 11 em produção agora?
Não, porque ainda é preview. O que faz sentido hoje é validar pipelines e ferramentas internas, para decidir em novembro com dados e não com suposições.