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.

Glossário

Termo Significado
AOT (Ahead of Time) Compilação para código nativo feita antes da execução, eliminando o custo de compilação em tempo de execução
NativeAOT Modelo de publicação do .NET que gera um executável nativo autocontido, sem JIT e sem inicialização do CoreCLR
CoreCLR Runtime principal do .NET, responsável por JIT, coleta de lixo e execução de código gerenciado
MSBuild server Processo de trabalho do MSBuild mantido vivo entre invocações do CLI para reaproveitar contexto de build
MTP (Microsoft.Testing.Platform) Plataforma de testes do .NET que substitui o VSTest como host de execução, com protocolo próprio
VSTest Plataforma de testes anterior do ecossistema .NET, ainda suportada e selecionável por configuração
TRX Formato XML de relatório de resultados de teste usado pelas ferramentas da Microsoft
RID (Runtime Identifier) Identificador da plataforma alvo de um artefato, como win-x64, linux-arm64 ou osx-arm64
Grafo portátil de RIDs Modelo simplificado de resolução de RIDs que substitui o grafo legado com identificadores por distribuição
Microsoft.Build.Traversal SDK do MSBuild usado para criar projetos que apenas agregam e orquestram outros projetos
Referência em diamante Situação em que dois caminhos do grafo de dependências chegam ao mesmo projeto
App baseado em arquivo Recurso do .NET 10 que permite executar um arquivo C# solto, sem arquivo de projeto
hostfxr Biblioteca nativa que localiza e carrega o runtime correto ao iniciar uma aplicação .NET
musl Implementação alternativa da biblioteca C padrão, usada por distribuições como o Alpine Linux
wslc CLI de contêiner nativa do Windows, integrada ao WSL
Podman Engine de contêineres sem daemon, compatível com a interface do Docker
Cancelamento cooperativo Modelo em que o processo alvo recebe uma notificação e finaliza de forma ordenada
STS (Standard Term Support) Categoria de release do .NET com dois anos de suporte
LTS (Long Term Support) Categoria de release do .NET com três anos de suporte
Avaliação parcial Recurso do MSBuild que interrompe a avaliação de um projeto após obter o dado necessário

Fontes