C# no .NET 11 Preview 7: controle de fluxo rotulado, uniões, exaustividade e segurança de memória
O C# no .NET 11 Preview 7 recebe seis mudanças no compilador Roslyn.
Embora pequenas, elas revelam a direção da equipe de design no ciclo do C# 15. As mudanças incluem sintaxe nova, ajustes semânticos em recursos em preview e correções de segurança no código gerado.
A seguir, você vê cada uma delas e o que muda na prática para quem já testa o SDK de preview.
O contexto do C# no .NET 11
O .NET 11 tem lançamento final previsto para novembro de 2026, e as versões de preview já trazem o C# 15.
O pacote inclui argumentos em expressões de coleção, tipos de união e hierarquias fechadas. Também chegam indexadores de extensão, saltos rotulados e um novo modelo de segurança de memória.
Três desses recursos aparecem diretamente nas notas do Preview 7. Isso indica que, nesta fase, o C# no .NET 11 está menos focado em introduzir sintaxe e mais em acertar semântica.
Vale registrar uma distinção importante para quem for testar.
Uniões e o modelo de segurança de memória exigem <LangVersion>preview</LangVersion> no projeto. Além disso, ambos ainda podem mudar até o lançamento final.
Break e continue rotulados no C# do .NET 11
O primeiro recurso é também o mais útil no dia a dia. A partir do C# 15, as instruções break e continue podem nomear um rótulo colocado em uma construção externa.
Até então, sair de um laço aninhado exigia um de dois contornos clássicos: uma flag booleana checada em cada nível externo ou um goto que saltava para além dos laços.
Ambos funcionam, mas ambos escondem a intenção do código. Afinal, a flag transforma controle de fluxo em estado, enquanto o goto obriga o leitor a procurar o rótulo de destino.
Com a sintaxe nova do C# no .NET 11, o rótulo fica sobre o laço alvo e aparece na instrução de salto.
string? foundValue = null;
outer: for (int x = 0; x < xMax; x++)
{
for (int y = 0; y < yMax; y++)
{
if (GetValue(x, y) is { } value && value == target)
{
foundValue = value;
break outer;
}
}
}
O mesmo rótulo funciona com continue, que inicia a próxima iteração do laço externo em vez de encerrá-lo.
row: for (int i = 0; i < rows; i++)
{
for (int j = 0; j < cols; j++)
{
// Pula o restante da linha e segue para a próxima
if (ShouldSkipRestOfRow(i, j)) continue row;
Process(i, j);
}
}
Regras dos rótulos
As regras são restritivas por design. O rótulo fica colado em um for, foreach, while, do ou switch, e o salto precisa estar dentro dessa instrução.
Há, porém, uma diferença sutil entre as duas instruções.
Segundo a documentação, o break rotulado sai de um laço ou de um switch. Já o continue rotulado só mira laços.
Sem rótulo, break e continue mantêm o comportamento original e continuam mirando a instrução mais interna. Dessa forma, o código existente segue totalmente compatível.
Além disso, a regra de estilo IDE0410 sinaliza os padrões de flag booleana e de goto que um salto rotulado pode substituir. Assim, o próprio editor ajuda a migrar o código antigo.
Padrões de união com tentativa dupla
A segunda mudança é a mais delicada do Preview 7 e merece atenção de quem acompanha as uniões desde os primeiros previews. Se o recurso ainda for novidade, o artigo sobre union types no .NET 11 apresenta os fundamentos.
Um tipo de união, declarado com union, representa um valor de um entre vários tipos de caso. Além disso, ele oferece conversões implícitas e permite checar exaustividade em switch.
Internamente, uma união guarda o conteúdo como uma única referência object?, exposta pela propriedade Value. Nos previews anteriores, o casamento de padrões seguia um modelo de desembrulho, aplicando o padrão a Value e não à própria união.
O Preview 7 troca esse modelo pela tentativa dupla.
Ou seja, o compilador testa o padrão primeiro contra a instância da união. Se o teste falhar, ele testa contra o Value contido.
Se qualquer um dos dois testes passar, o padrão é satisfeito. Na prática, uma expressão como pet is Pet passa a ser verdadeira, porque o tipo da união vira um alvo válido de correspondência.
public record class Dog(string Name);
public record class Cat(int Lives);
public union Pet(Dog, Cat);
Pet pet = new Cat(9);
// Correspondência contra a própria instância da união
if (pet is Pet)
Console.WriteLine("got a pet");
// Correspondência contra o valor contido
if (pet is Cat { Lives: > 0 } cat)
Console.WriteLine($"cat has {cat.Lives} lives");
A mudança vale para padrões de tipo, var, declaração, lista e padrões recursivos. Portanto, ela cobre praticamente toda a superfície de correspondência da linguagem.
Documentação defasada no C# no .NET 11
Aqui cabe um alerta de leitura. A referência de tipos de união no Microsoft Learn ainda descreve o desembrulho, inclusive afirmando que pet is Pet normalmente não corresponde.
Por isso, quem testa o C# no .NET 11 Preview 7 deve tratar as notas de release como a fonte mais atual. A expectativa é que a página de referência seja atualizada nos próximos previews.
Duas alterações menores acompanham essa mudança. Primeiro, o binding deixa de propagar o tipo de união quando o valor de saída se estreita para um tipo de caso.
Em seguida, declarações de união customizadas ganham a propriedade UnionMatchingMode, que controla como os padrões são reduzidos. Ela substitui sinalizadores improvisados que existiam antes.
Exaustividade com hierarquias fechadas no C# no .NET 11
O terceiro item do C# no .NET 11 Preview 7 conecta duas funcionalidades que evoluíram em paralelo: hierarquias fechadas e verificação de exaustividade.
O modificador closed aplicado a uma classe declara uma hierarquia fechada. Nesse caso, subtipos diretos só podem existir dentro do assembly declarante, o que fixa o conjunto de descendentes em tempo de compilação.
Como o compilador conhece todos os descendentes diretos, um switch que trate cada um deles é exaustivo e dispensa o braço padrão. A novidade é suportar essa análise quando a entrada é um parâmetro genérico restrito ao tipo fechado.
public closed record class Shape;
public record class Circle(double Radius) : Shape;
public record class Square(double Side) : Shape;
static double Area<T>(T shape) where T : Shape => shape switch
{
Circle(var r) => Math.PI * r * r,
Square(var s) => s * s
};
O compilador conclui que T não introduz nenhum caso extra, porque os braços já cobrem todo tipo derivado de Shape. Segundo a documentação, a regra vale tanto para parâmetros de método quanto para parâmetros do tipo que os contém.
Restrições e metadados estáveis
Duas restrições continuam valendo. A derivação não é transitiva, de modo que um descendente não fechado de uma classe fechada ainda aceita extensão em outros assemblies.
Além disso, quando a expressão do switch é anulável, null vira mais um valor que precisa de tratamento para o switch ser exaustivo.
O Preview 7 também estabiliza o formato de metadados das hierarquias fechadas. O atributo IsClosedTypeAttribute passa a carregar a propriedade DerivedTypes, no formato definido nas discussões de runtime.
Essa estabilização importa porque é ela que permite a exaustividade entre assemblies. Sem ela, a análise ficaria restrita à unidade de compilação onde a hierarquia nasceu.
Unsafe Evolution: compatibilidade e nameof
O quarto item faz parte do esforço de várias versões para redefinir o que significa segurança de memória em C#. No modelo original, o contexto unsafe estava amarrado à mera existência de recursos de ponteiro.
Declarar um tipo ponteiro, obter o endereço de uma variável, usar fixed ou aplicar sizeof a um tipo arbitrário exigia unsafe. O modelo atualizado desloca essa fronteira para as operações que realmente acessam memória não gerenciada pelo runtime.
A outra metade do modelo é a propagação.
Marcar um membro como unsafe passa a ser um contrato. O membro se torna requires-unsafe, e quem o chama herda a obrigação de auditar a segurança.
Um assembly adere a essas regras de forma explícita, e o compilador registra a escolha com MemorySafetyRulesAttribute. Enquanto isso, membros que exigem contexto inseguro recebem RequiresUnsafeAttribute nos metadados.
As duas regras do Preview 7
A primeira regra estende o modo de compatibilidade a chamadores legados. Assim, membros requires-unsafe passam a exigir contexto unsafe mesmo quando chamados de código que não aderiu às regras novas.
O motivo é direto.
Sem essa extensão, projetos que só elevaram o LangVersion ficariam menos protegidos do que antes. Afinal, chamariam membros requires-unsafe a partir de contextos seguros.
No modo de compatibilidade, um membro de módulo não atualizado conta como requires-unsafe quando a assinatura contém ponteiro ou ponteiro de função. Isso vale inclusive quando o ponteiro aparece aninhado em outro tipo.
A segunda regra corrige um falso positivo: referenciar um membro requires-unsafe dentro de nameof(...) deixa de gerar erro. Afinal, nameof nunca avalia o membro nem acessa memória alguma.
Ambas as regras do C# no .NET 11 continuam em preview, e a especificação deixa claro que diagnósticos e detalhes ainda podem mudar.
Inline arrays mais seguros no C# do .NET 11
O quinto item é uma correção de segurança, e não uma mudança de linguagem. Mesmo assim, é a que mais impacta código já existente.
Um inline array é uma struct decorada com InlineArrayAttribute, cujo layout o runtime estende para acomodar um número fixo de elementos em sequência. O compilador reduz o acesso a elementos e a conversão para Span<T> a helpers sintetizados em <PrivateImplementationDetails>.
Entre eles estão InlineArrayElementRef, InlineArrayElementRefReadOnly, InlineArrayAsSpan e InlineArrayAsReadOnlySpan. O problema é que esses helpers aceitavam qualquer byref, inclusive um nulo, e devolviam uma referência em um deslocamento controlado pelo chamador.
Na prática, ler ou escrever pelo resultado não falhava de forma confiável.
Em vez disso, gerava acessos arbitrários à memória. Pior: código seguro alcançava esse caminho, por exemplo com Unsafe.NullRef<T>().
Agora os helpers verificam se o buffer recebido é nulo, e o código passa a lançar NullReferenceException de forma determinística. Além disso, o custo é praticamente zero, porque o JIT elimina a verificação quando prova que a base não é nula.
Nenhuma alteração de código é necessária para receber a correção. Basta recompilar o projeto com o compilador do C# no .NET 11 Preview 7.
Diagnóstico de runtime-async mais claro
O último item ajusta a mensagem de um diagnóstico do runtime-async. Nesse modelo, o runtime, e não o compilador, gerencia a suspensão e a retomada de métodos assíncronos.
O runtime-async troca as máquinas de estado geradas pelo compilador por rastreamento no próprio runtime.
Com isso, a pilha de chamadas fica mais limpa, a depuração melhora e a sobrecarga diminui. Inclusive, as bibliotecas do runtime no .NET 11 já usam runtime-async=on.
O diagnóstico em questão avisa que um método usa algo que o runtime-async não suporta. Contudo, a redação anterior sugeria que o suporte chegaria um dia, o que é enganoso no caso de __arglist.
Como essa construção não passa pela transformação do runtime-async, a mensagem agora diz isso de forma direta. Por fim, o diagnóstico saiu da lista exclusiva de build, porque agora só surge no binding inicial.
Para conhecer as demais mudanças de runtime desta versão, veja o artigo sobre o runtime do .NET 11 Preview 7.
Como adotar o C# no .NET 11 Preview 7
Nenhum dos recursos discutidos aqui deve ir para produção neste momento. Afinal, uniões, hierarquias fechadas e o modelo de segurança de memória continuam classificados como preview.
Para experimentar o C# no .NET 11, instale o SDK de preview. Depois, para os recursos instáveis, adicione LangVersion preview ao projeto.
<PropertyGroup>
<TargetFramework>net11.0</TargetFramework>
<LangVersion>preview</LangVersion>
</PropertyGroup>
A exceção é a correção dos helpers de inline array. Ela não depende de opt-in, não altera código e fecha um vetor de escrita arbitrária alcançável a partir de código seguro.
Já quem mantém bibliotecas com APIs baseadas em ponteiros precisa ler o modo de compatibilidade com atenção. Isso porque a regra de propagação muda a superfície de compilação de quem consome essas bibliotecas.
O Preview 7 também trouxe novidades em outras partes da plataforma. Para ter a visão completa, vale ler sobre as novidades do ASP.NET Core no .NET 11 Preview 7 e sobre o EF Core 11 no Preview 7.
Outro recurso do C# 15 que vale acompanhar é o de argumentos em expressões de coleção. O artigo sobre collection expression arguments no C# 15 mostra como ele funciona.
Para quem acompanha as uniões, a tentativa dupla é o tipo de mudança que quebra suposições em silêncio. Em outras palavras, código escrito contra o desembrulho pode continuar compilando e mudar de comportamento.
Conclusão
O Preview 7 é um preview de consolidação para o C# no .NET 11. Ele traz apenas um recurso de sintaxe realmente novo, o break e continue rotulados, e dedica o restante do esforço a fechar arestas.
Essa distribuição é saudável nesta altura do ciclo. Com o lançamento previsto para novembro de 2026, o esperado é justamente estabilizar metadados, corrigir falsos positivos e fechar brechas de segurança.
O ponto que exige mais atenção é a mudança semântica nas uniões. Ela afeta código experimental e ainda diverge da página de referência.
Para quem ensina, produz conteúdo ou mantém bibliotecas, o momento é de testar cedo e reportar cedo. Afinal, a equipe do Roslyn segue aberta a feedback, e recursos em preview ainda podem mudar com o retorno da comunidade.
FAQ
Preciso de configuração especial para usar break e continue rotulados?
Você precisa do SDK de preview do .NET 11 e do compilador do C# 15. Segundo as notas originais, o recurso não depende de LangVersion preview da mesma forma que uniões e o novo modelo de memória.
A mudança nos padrões de união quebra código dos previews anteriores?
Pode mudar o comportamento sem quebrar a compilação, já que pet is Pet agora corresponde. Como uniões estão em preview, esse tipo de mudança é esperado.
Por que a documentação diverge das notas de release sobre uniões?
A página de referência ainda descreve o desembrulho dos previews anteriores. Já as notas do Preview 7 descrevem a tentativa dupla, e a referência tende a ser atualizada.
Preciso alterar meu código para receber a correção de inline arrays?
Não, a correção está no código que o compilador gera. Basta recompilar com o compilador do Preview 7.
Meu código com inline arrays pode passar a lançar exceções?
Sim, se ele formava uma referência a partir de um inline array nulo. Antes isso gerava acessos arbitrários à memória; agora lança NullReferenceException.
Hierarquias fechadas já funcionam entre assemblies?
O formato de metadados ficou estável neste preview com a propriedade DerivedTypes em IsClosedTypeAttribute. É esse mecanismo que permite ao compilador conhecer os descendentes de um tipo de outro assembly.
O runtime-async já está pronto para produção?
Não, ele continua em preview e se habilita com <Features>runtime-async=on</Features> no projeto. Ainda assim, as bibliotecas do runtime do .NET 11 já são compiladas com ele ligado.