Alterações de destaque em Roslyn após .NET 10.0.100 até .NET 11.0.100

Este documento lista alterações disruptivas conhecidas no Roslyn após a versão geral do .NET 10 (SDK .NET versão 10.0.100) até à versão geral do .NET 11 (SDK .NET versão 11.0.100).

O safe-context de uma expressão de coleção do tipo Span/ReadOnlySpan é agora declaration-block

Introduzido em Visual Studio 2026 versão 18.3

O compilador C# fez uma alteração crítica para aderir corretamente às regras de segurança das referências na especificação das expressões de coleção. Especificamente, a seguinte cláusula:

  • Se o tipo de destino for um tipo de segmento System.Span<T> ou System.ReadOnlySpan<T>, o contexto seguro da expressão de coleção será o bloco de declaração .

Anteriormente, o compilador usava o safe-context function-member nesta situação. Agora fizemos uma alteração para usar o bloco de declaração conforme a especificação. Isto pode causar o surgimento de novos erros no código existente, como no cenário abaixo:

scoped Span<int> items1 = default;
scoped Span<int> items2 = default;
foreach (var x in new[] { 1, 2 })
{
    Span<int> items = [x];
    if (x == 1)
        items1 = items; // previously allowed, now an error

    if (x == 2)
        items2 = items; // previously allowed, now an error
}

Se o seu código for afetado por esta alteração, considere usar um tipo de array para as expressões de coleção relevantes:

scoped Span<int> items1 = default;
scoped Span<int> items2 = default;
foreach (var x in new[] { 1, 2 })
{
    int[] items = [x];
    if (x == 1)
        items1 = items; // ok, using 'int[]' conversion to 'Span<int>'

    if (x == 2)
        items2 = items; // ok
}

Alternativamente, mova a expressão de coleção para um âmbito onde a atribuição seja permitida:

scoped Span<int> items1 = default;
scoped Span<int> items2 = default;
Span<int> items = [0];
foreach (var x in new[] { 1, 2 })
{
    items[0] = x;
    if (x == 1)
        items1 = items; // ok

    if (x == 2)
        items2 = items; // ok
}

Ver também https://github.com/dotnet/csharplang/issues/9750.

Cenários que exigem que o compilador sintetize um ref readonly delegado que retorna agora exigem disponibilidade do System.Runtime.InteropServices.InAttribute tipo.

Introduzido no Visual Studio 2026 versão 18.3

O compilador C# fez uma alteração decisiva para emitir corretamente metadados para ref readonlyos delegados sintetizados que retornavam

Isto pode causar o aparecimento de um "erro CS0518: O tipo pré-definido 'System.Runtime.InteropServices.InAttribute' não está definido ou importado" no código existente, como nos cenários abaixo:

var d = this.MethodWithRefReadonlyReturn;
var d = ref readonly int () => ref x;

Se o teu código for afetado por esta alteração de rutura, considera adicionar uma referência a um assembly que define System.Runtime.InteropServices.InAttribute no teu projeto.

Os cenários que ref readonly utilizam funções locais agora exigem disponibilidade do System.Runtime.InteropServices.InAttribute tipo.

Introduzido no Visual Studio 2026, versão 18.3

O compilador C# fez uma alteração significativa para poder emitir corretamente metadados para funções locais de retorno ref readonly.

Isto pode causar que apareça um "erro CS0518: O tipo pré-definido 'System.Runtime.InteropServices.InAttribute' não está definido ou importado" no código existente, como no cenário abaixo:

void Method()
{
    ...
    ref readonly int local() => ref x;
    ...
}

Se o teu código for afetado por esta alteração de rutura, considera adicionar uma referência a um assembly que define System.Runtime.InteropServices.InAttribute no teu projeto.

A avaliação dinâmica dos operadores &&/|| não é permitida com o operando esquerdo tipado estaticamente como uma interface.

Introduzido no Visual Studio 2026 versão 18.3

O compilador C# agora gera um erro quando um tipo de interface é usado como operando esquerdo de um operador lógico && ou || com um operando dynamic à direita. Anteriormente, o código compilava para um tipo de interface com true/false operadores, mas falhava em tempo de execução com a RuntimeBinderException porque o binder de runtime não pode invocar operadores definidos em interfaces.

Esta alteração previne um erro em tempo de execução ao sinalizá-lo em tempo de compilação. A mensagem de erro é:

erro CS7083: A expressão deve ser implicitamente convertível para booleano ou o seu tipo 'I1' não deve ser uma interface e deve definir o operador 'falso'.

interface I1
{
    static bool operator true(I1 x) => false;
    static bool operator false(I1 x) => false;
}

class C1 : I1
{
    public static C1 operator &(C1 x, C1 y) => x;
    public static bool operator true(C1 x) => false;
    public static bool operator false(C1 x) => false;
}

void M()
{
    I1 x = new C1();
    dynamic y = new C1();
    _ = x && y; // error CS7083: Expression must be implicitly convertible to Boolean or its type 'I1' must not be an interface and must define operator 'false'.
}

Se o seu código for afetado por esta alteração disruptiva, considere alterar o tipo estático do operando esquerdo de um tipo de interface para um tipo de classe concreta, ou para o tipo dynamic.

void M()
{
    I1 x = new C1();
    dynamic y = new C1();
    _ = (C1)x && y; // Valid - uses operators defined on C1
    _ = (dynamic)x && y; // Valid - uses operators defined on C1
}

Ver também https://github.com/dotnet/roslyn/issues/80954.

nameof(this.) em atributos não é permitido

Introduzido no Visual Studio 2026 versão 18.3 e .NET 10.0.200

O uso de this ou base palavra-chave dentro de um atributo nameof foi anteriormente permitido involuntariamente no Roslyn desde C# 12 e agora está devidamente proibido para alinhar com a especificação da linguagem. Esta alteração pode ser mitigada removendo this. e acedendo ao membro sem o qualificador.

Ver também https://github.com/dotnet/roslyn/issues/82251.

class C
{
    string P;
    [System.Obsolete(nameof(this.P))] // now disallowed
    [System.Obsolete(nameof(P))] // workaround
    void M() { }
}

Análise de 'com' dentro de um braço de expressão de troca

Introduzido em 2026 na versão 18.4 do Visual Studio

Ver https://github.com/dotnet/roslyn/issues/81837 e https://github.com/dotnet/roslyn/pull/81863

Anteriormente, ao ver o seguinte, o compilador tratava (X.Y)when como uma expressão de cast. Especificamente, convertendo o identificador contextual when para (X.Y):

x switch
{
    (X.Y) when
}

Isto era indesejável e significava que uma simples when verificação do padrão (como (X.Y) when a > b =>) não faria a análise adequada. Agora, isto é tratado como um padrão (X.Y) constante seguido de um when clause.

with()como elemento de expressão de coleção, é tratado como argumentos de construção de coleções

Introduzido no Visual Studio 2026 versão 18.4

with(...) quando usado como elemento numa expressão de coleção, e quando o LangVersion está definido como 15 ou superior, é vinculado como argumentos passados ao construtor ou método de fábrica usado para criar a coleção, em vez de como uma expressão de invocação de um método chamado with.

Para ligar a um método chamado with, use @with em vez disso.

object x, y, z = ...;
object[] items;

items = [with(x, y), z];  // C# 14: call to with() method; C# 15: error args not supported for object[]
items = [@with(x, y), z]; // call to with() method
object with(object a, object b) { ... }

Os tipos de ponteiros já não requerem um contexto inseguro

Introduzido em Visual Studio 2026 versão 18.7

Numa versão futura de C# (atualmente em langversion:preview), os tipos de ponteiro (por exemplo, int*, delegate*<void>) já não requerem um contexto inseguro. Apenas as operações de indireção do ponteiro (desreferência, acesso de membros via ->, acesso a elementos, etc.) exigem insegurança. Isto faz parte da funcionalidade de evolução insegura .

Como os tipos de ponteiros são agora permitidos em contextos seguros, a resolução por sobrecarga pode considerar candidatos que anteriormente estavam excluídos. Isto pode causar novos erros de ambiguidade:

using System;

class Program
{
    static void Main()
    {
        M(x => { }); // C# 14: prints "2"; C# preview: error CS0121 (ambiguous)
    }

    static void M(F1 f) { Console.WriteLine(1); }
    static void M(F2 f) { Console.WriteLine(2); }
}

unsafe delegate void F1(int* x);
delegate void F2(int x);

Anteriormente, a lambda x => { } não podia converter para F1 num contexto seguro porque int* exigia um contexto inseguro, sendo assim apenas M(F2) aplicável. Agora que int* é legal em contextos seguros, a lambda é convertível para ambos os delegados e produz um erro de ambiguidade.

Se o seu código for afetado pela alteração de ambiguidade, adicione tipos de parâmetros explícitos ao lambda para desambiguar:

M((int x) => { }); // Resolves to M(F2)

Um stackalloc não inicializado dentro de SkipLocalsInit requer um contexto inseguro

Introduzido no Visual Studio 2026, versão 18.7

Numa versão futura em C# (atualmente em langversion:preview), uma stackalloc expressão sem inicializador dentro de um método marcado com [SkipLocalsInit] agora requer um contexto inseguro, mesmo quando o tipo alvo é Span<T>. Isto deve-se ao facto SkipLocalsInit de impedir a inicialização zero da memória alocada, tornando possível ler dados não inicializados – uma preocupação de segurança de memória. Isto faz parte da funcionalidade de evolução insegura .

[System.Runtime.CompilerServices.SkipLocalsInit]
void M()
{
    Span<int> a = stackalloc int[5];           // previously ok, now error CS9361
    Span<int> b = stackalloc int[] { 1, 2 };   // ok (has initializer)
    Span<int> c = stackalloc int[2] { 1, 2 };  // ok (has initializer)
}

Se o teu código for afetado, podes de qualquer forma:

  • Adicione um unsafe bloco à volta do stackalloc:
    [SkipLocalsInit]
    void M()
    {
        Span<int> a;
        unsafe { a = stackalloc int[5]; }
    }
    
  • Ou fornecer um inicializador para que a memória fique totalmente inicializada:
    [SkipLocalsInit]
    void M()
    {
        Span<int> a = stackalloc int[5] { 0, 0, 0, 0, 0 };
    }