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

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

O contexto seguro de uma expressão de coleção do tipo Span/ReadOnlySpan agora é bloco de declaração

Introduzido no Visual Studio 2026 versão 18.3

O compilador C# fez uma alteração significativa para aderir corretamente às regras de segurança de ref na especificação do recurso de expressões de coleção . Especificamente, a seguinte cláusula:

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

Anteriormente, o compilador usava o membro-função de contexto seguro nessa situação. Agora, fizemos uma alteração para usar o declaration-block conforme a especificação. Isso pode fazer com que novos erros apareçam 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 código for afetado por essa alteração significativa, considere usar um tipo de matriz para as expressões de coleção relevantes em vez disso:

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
}

Como alternativa, mova a expressão de coleção para um escopo em que a atribuição é 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
}

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

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

Introduzido no Visual Studio 2026 versão 18.3

O compilador C# fez uma alteração significativa para emitir metadados corretamente para ref readonly retornar delegados sintetizados

Isso pode fazer com que um "erro CS0518: tipo predefinido 'System.Runtime.InteropServices.InAttribute' não seja definido ou importado" apareça no código existente, como nos cenários abaixo:

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

Se o seu código for impactado por essa alteração disruptiva, considere adicionar uma referência a um assembly que define System.Runtime.InteropServices.InAttribute no seu projeto.

Cenários que utilizam ref readonly 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 emitir metadados corretamente para ref readonly retornar funções locais.

Isso pode fazer com que um "erro CS0518: tipo predefinido 'System.Runtime.InteropServices.InAttribute' não seja definido ou importado" apareça no código existente, como no cenário abaixo:

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

Se o seu código for impactado por essa alteração disruptiva, considere adicionar uma referência a um assembly que define System.Runtime.InteropServices.InAttribute no seu 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 relata um erro quando um tipo de interface é usado como o operando esquerdo de um operador lógico && ou ||, com um operando dynamic à direita. Anteriormente, o código era compilado para um tipo de interface com operadores true/false, mas falhava em tempo de execução com um RuntimeBinderException porque o associador de tempo de execução não pode invocar operadores definidos em interfaces.

Essa alteração impede um erro de runtime relatando-o em tempo de compilação. A mensagem de erro é:

erro CS7083: a expressão deve ser implicitamente conversível para Boolean ou seu tipo 'I1' não deve ser uma interface e deve definir o operador 'false'.

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 código for impactado por essa alteração, 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
}

Consulte 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 das palavras-chave this ou base dentro de um atributo foi anteriormente permitido de forma involuntária no Roslyn desde o C# 12 e agora está corretamente proibido para corresponder à especificação da linguagem. Esta mudança disruptiva pode ser mitigada removendo this. e acessando o membro sem o qualificador.

Consulte 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 'with' dentro de um switch-expression-arm

Introduzido no Visual Studio 2026 versão 18.4

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

Anteriormente, ao ver o seguinte, o compilador trataria (X.Y)when como uma expressão de conversão. Especificamente, convertendo o identificador when contextual em (X.Y):

x switch
{
    (X.Y) when
}

Isso era indesejável, e significava que uma verificação simples when do padrão (como (X.Y) when a > b =>) não analisaria corretamente. Agora, isso é tratado como um padrão (X.Y) constante seguido por um when clause.

with()como um elemento de expressão de coleção é tratado como argumentos de construção de coleção

Introduzido no Visual Studio 2026 versão 18.4

with(...) quando usado como um elemento em uma expressão de coleção e quando o LangVersion é definido como 15 ou maior, é associado como argumentos passados para o 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 associar 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) { ... }

Tipos de ponteiro não exigem mais um contexto não seguro

Introduzido no Visual Studio 2026 versão 18.7

Em uma versão futura do C# (atualmente em langversion:preview), os tipos de ponteiro (por exemplo, int*, ) delegate*<void>não exigem mais um contexto não seguro. Somente as operações de indireção de ponteiro (desreferência, acesso de membro via ->, acesso a elementos etc.) exigem o uso de 'unsafe'. Isso faz parte do recurso de evolução não segura .

Como os tipos de ponteiro agora são permitidos em contextos seguros, a resolução de sobrecarga pode considerar candidatos que foram excluídos anteriormente. Isso 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, o lambda x => { } não podia converter F1 em um contexto seguro porque int* exigia um contexto não seguro, portanto, só M(F2) era aplicável. Agora que int* é legal em contextos seguros, o lambda pode ser convertido para ambos os delegados, o que resulta em um erro de ambiguidade.

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

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

Variável não inicializada stackalloc dentro SkipLocalsInit requer um contexto não seguro

Introduzido no Visual Studio 2026 versão 18.7

Em uma versão futura do C# (atualmente em langversion:preview), uma stackalloc expressão sem um inicializador dentro de um método marcado com [SkipLocalsInit] agora requer um contexto não seguro, mesmo quando o tipo de destino é Span<T>. Isso ocorre porque SkipLocalsInit impede a inicialização zero da memória alocada, possibilitando a leitura de dados não inicializados – uma preocupação de segurança de memória. Isso faz parte do recurso de evolução não segura .

[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 código for afetado, você poderá:

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