Compartilhar via


Alterações interruptivas no Roslyn após o .NET 9.0.100 até o .NET 10.0.100

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

scoped em uma lista de parâmetros lambda agora é sempre um modificador.

Introduzida no Visual Studio 2022 versão 17.13

O C# 14 apresenta a capacidade de escrever um lambda com modificadores de parâmetro, sem precisar especificar um tipo de parâmetro: parâmetros lambda simples com modificadores

Como parte desse trabalho, foi aceita uma alteração interruptiva na qual scoped sempre será tratado como modificador em um parâmetro lambda, mesmo onde anteriormente poderia ter sido aceito como nome de tipo. Por exemplo:

var v = (scoped scoped s) => { ... };

ref struct @scoped { }

No C# 14, isso será um erro, pois ambos os tokens scoped são tratados como modificadores. A solução alternativa é usar @ na posição do nome do tipo, da seguinte maneira:

var v = (scoped @scoped s) => { ... };

ref struct @scoped { }

As sobrecargas de Span<T> e ReadOnlySpan<T> são aplicáveis em mais cenários no C# 14 e mais recente

Introduzida no Visual Studio 2022 versão 17.13

O C# 14 apresenta novas conversões de extensão embutidas e regras de inferência de tipo. Isso significa que diferentes sobrecargas podem ser escolhidas em comparação com o C# 13 e, às vezes, pode ocorrer um erro de ambiguidade em tempo de compilação porque uma nova sobrecarga é aplicável, mas não há uma única melhor opção de sobrecarga.

O exemplo a seguir mostra algumas ambiguidades e soluções alternativas possíveis. Outra solução alternativa é que os autores de API usem OverloadResolutionPriorityAttribute.

var x = new long[] { 1 };
Assert.Equal([2], x); // previously Assert.Equal<T>(T[], T[]), now ambiguous with Assert.Equal<T>(ReadOnlySpan<T>, Span<T>)
Assert.Equal([2], x.AsSpan()); // workaround

var y = new int[] { 1, 2 };
var s = new ArraySegment<int>(y, 1, 1);
Assert.Equal(y, s); // previously Assert.Equal<T>(T, T), now ambiguous with Assert.Equal<T>(Span<T>, Span<T>)
Assert.Equal(y.AsSpan(), s); // workaround

Uma sobrecarga Span<T> pode ser escolhida no C# 14, em que uma sobrecarga que utiliza uma interface implementada por T[] (como IEnumerable<T>) foi escolhida no C# 13 e isso pode levar a um ArrayTypeMismatchException em runtime se usado com uma matriz covariante:

string[] s = new[] { "a" };
object[] o = s; // array variance

C.R(o); // wrote 1 previously, now crashes in Span<T> constructor with ArrayTypeMismatchException
C.R(o.AsEnumerable()); // workaround

static class C
{
    public static void R<T>(IEnumerable<T> e) => Console.Write(1);
    public static void R<T>(Span<T> s) => Console.Write(2);
    // another workaround:
    public static void R<T>(ReadOnlySpan<T> s) => Console.Write(3);
}

Por esse motivo, ReadOnlySpan<T> geralmente é preferido em vez de Span<T> pela resolução de sobrecarga no C# 14. Em alguns casos, isso pode causar falhas na compilação, por exemplo, quando existem sobrecargas tanto para Span<T> quanto para ReadOnlySpan<T>, ambos utilizando e retornando o mesmo tipo de span.

double[] x = new double[0];
Span<ulong> y = MemoryMarshal.Cast<double, ulong>(x); // previously worked, now compilation error
Span<ulong> z = MemoryMarshal.Cast<double, ulong>(x.AsSpan()); // workaround

static class MemoryMarshal
{
    public static ReadOnlySpan<TTo> Cast<TFrom, TTo>(ReadOnlySpan<TFrom> span) => default;
    public static Span<TTo> Cast<TFrom, TTo>(Span<TFrom> span) => default;
}

Enumerable.Reverse

Ao usar o C# 14 ou mais recente e direcionar um .NET mais antigo ou net10.0 .NET Framework com System.Memory referência, há uma alteração significativa com Enumerable.Reverse matrizes e matrizes.

Cuidado

Isso só afeta os clientes que usam o C# 14 e direcionam o .NET anteriormente net10.0, que é uma configuração sem suporte.

int[] x = new[] { 1, 2, 3 };
var y = x.Reverse(); // previously Enumerable.Reverse, now MemoryExtensions.Reverse

Em net10.0, há Enumerable.Reverse(this T[]) que tem precedência e, portanto, a interrupção é evitada. Caso contrário, MemoryExtensions.Reverse(this Span<T>) será resolvido, o que tem semântica diferente de Enumerable.Reverse(this IEnumerable<T>) (que costumava ser resolvida no C# 13 e versões anteriores). Especificamente, a extensão Span faz a reversão no local e retorna void. Como solução alternativa, é possível definir seus próprios Enumerable.Reverse(this T[]) ou usar Enumerable.Reverse explicitamente:

int[] x = new[] { 1, 2, 3 };
var y = Enumerable.Reverse(x); // instead of 'x.Reverse();'

Os diagnósticos agora são relatados para o método de descarte por padrões no foreach

Introduzida no Visual Studio 2022 versão 17.13

Por exemplo, um método DisposeAsync obsoleto agora é relatado em await foreach.

await foreach (var i in new C()) { } // 'C.AsyncEnumerator.DisposeAsync()' is obsolete

class C
{
    public AsyncEnumerator GetAsyncEnumerator(System.Threading.CancellationToken token = default)
    {
        throw null;
    }

    public sealed class AsyncEnumerator : System.IAsyncDisposable
    {
        public int Current { get => throw null; }
        public Task<bool> MoveNextAsync() => throw null;

        [System.Obsolete]
        public ValueTask DisposeAsync() => throw null;
    }
}

Definir o estado do objeto enumerador como "depois" durante o descarte

Introduzida no Visual Studio 2022 versão 17.13

O computador de estado para enumeradores permitiu incorretamente a retomada da execução depois que o enumerador foi descartado.
Agora, MoveNext() em um enumerador descartado retorna corretamente false sem executar mais nenhum código de usuário.

var enumerator = C.GetEnumerator();

Console.Write(enumerator.MoveNext()); // prints True
Console.Write(enumerator.Current); // prints 1

enumerator.Dispose();

Console.Write(enumerator.MoveNext()); // now prints False

class C
{
    public static IEnumerator<int> GetEnumerator()
    {
        yield return 1;
        Console.Write("not executed after disposal")
        yield return 2;
    }
}

Avisar sobre padrão redundante em or padrão simples

Introduzida no Visual Studio 2022 versão 17.13

Em um padrão disjuntivo or como is not null or 42 ou is not int or string, o segundo padrão é redundante e provavelmente resulta de um mal-entendido da ordem de precedência dos combinadores de padrão not e or.
O compilador fornece um aviso em casos comuns deste erro:

_ = o is not null or 42; // warning: pattern "42" is redundant
_ = o is not int or string; // warning: pattern "string" is redundant

É provável que o usuário tenha querido dizer is not (null or 42) ou is not (int or string) em vez disso.

UnscopedRefAttribute não pode ser usado com regras de segurança de ref antigas

Introduzida no Visual Studio 2022 versão 17.13

O UnscopedRefAttribute, de forma não intencional, afetou o código quando compilado pelas novas versões do compilador Roslyn, mesmo quando o código foi compilado no contexto das regras de segurança ref anteriores (ou seja, visando o C# 10 ou anteriores com o net6.0 ou versões anteriores). No entanto, o atributo não deve ter um efeito nesse contexto e agora isso foi corrigido.

O código que anteriormente não relatava erros no C# 10 ou anterior com net6.0 ou anterior agora pode falhar ao compilar:

using System.Diagnostics.CodeAnalysis;
struct S
{
    public int F;

    // previously allowed in C# 10 with net6.0
    // now fails with the same error as if the [UnscopedRef] wasn't there:
    // error CS8170: Struct members cannot return 'this' or other instance members by reference
    [UnscopedRef] public ref int Ref() => ref F;
}

Para evitar mal-entendidos (pensando que o atributo tem um efeito, mas na verdade não tem porque o código é compilado com as regras de segurança de ref anteriores), um aviso é relatado quando o atributo é usado no C# 10 ou anterior com net6.0 ou anterior:

using System.Diagnostics.CodeAnalysis;
struct S
{
    // both are errors in C# 10 with net6.0:
    // warning CS9269: UnscopedRefAttribute is only valid in C# 11 or later or when targeting net7.0 or later.
    [UnscopedRef] public ref int Ref() => throw null!;
    public static void M([UnscopedRef] ref int x) { }
}

Microsoft.CodeAnalysis.EmbeddedAttribute passa por validação na declaração

Introduzida no Visual Studio 2022 versão 17.13

O compilador agora valida a forma de Microsoft.CodeAnalysis.EmbeddedAttribute quando declarado na origem. Anteriormente, o compilador permitia declarações definidas pelo usuário desse atributo, mas somente quando não precisava gerar uma. Agora, validamos que:

  1. Deve ser interno
  2. Deve ser uma classe
  3. Deve ser selada
  4. Não deve ser estático
  5. Deve ter um construtor interno ou público sem parâmetros
  6. Deve herdar de System.Attribute.
  7. Ele pode ser permitido em qualquer declaração de tipo (classe, struct, interface, enum ou delegado)
namespace Microsoft.CodeAnalysis;

// Previously, sometimes allowed. Now, CS9271
public class EmbeddedAttribute : Attribute {}

A expressão field em um acessador de propriedades refere-se ao campo de suporte sintetizado

introduzido no Visual Studio 2022 versão 17.12

A expressão field, quando usada em um acessador de propriedade, refere-se a um campo de backup sintetizado para a propriedade.

O aviso CS9258 é gerado quando o identificador teria sido vinculado a um símbolo diferente nas versões 13 ou anteriores da linguagem.

Para evitar gerar um campo de backup sintetizado e fazer referência ao membro existente, use "this.field" ou "@field". Como alternativa, renomeie o membro existente e a referência a esse membro para evitar um conflito com field.

class MyClass
{
    private int field = 0;

    public object Property
    {
        get
        {
            // warning CS9258: The 'field' keyword binds to a synthesized backing field for the property.
            // To avoid generating a synthesized backing field, and to refer to the existing member,
            // use 'this.field' or '@field' instead.
            return field;
        }
    }
}

Variável chamada field não permitida em um acessador de propriedade

Introduzido no Visual Studio 2022 versão 17.14

A expressão field, quando usada em um acessador de propriedade, refere-se a um campo de backup sintetizado para a propriedade.

O erro CS9272 é relatado quando um local ou um parâmetro de uma função aninhada, com o nome field, é declarado em um acessador de propriedade.

Para evitar o erro, renomeie a variável ou use @field na declaração.

class MyClass
{
    public object Property
    {
        get
        {
            // error CS9272: 'field' is a keyword within a property accessor.
            // Rename the variable or use the identifier '@field' instead.
            int field = 0;
            return @field;
        }
    }
}

Os tipos record e record struct não podem definir membros do tipo ponteiro, mesmo quando fornecem suas próprias implementações de Equals

Introduzido no Visual Studio 2022 versão 17.14

A especificação para tipos record class e record struct indicava que não são permitidos quaisquer tipos de ponteiro como campos de instância. No entanto, isso não foi aplicado corretamente quando o tipo record class ou record struct definiu sua própria implementação de Equals.

O compilador agora proíbe corretamente isso.

unsafe record struct R(
    int* P // Previously fine, now CS8908
)
{
    public bool Equals(R other) => true;
}

A geração de executáveis que contêm apenas metadados requer um ponto de entrada

Introduzido no Visual Studio 2022 versão 17.14

Anteriormente, o ponto de entrada era inadvertidamente não definido ao emitir executáveis no modo somente metadados (também conhecido como ref assemblies). Isso agora está corrigido, mas também significa que um ponto de entrada ausente é um erro de compilação:

// previously successful, now fails:
CSharpCompilation.Create("test").Emit(new MemoryStream(),
    options: EmitOptions.Default.WithEmitMetadataOnly(true))

CSharpCompilation.Create("test",
    // workaround - mark as DLL instead of EXE (the default):
    options: new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary))
    .Emit(new MemoryStream(),
        options: EmitOptions.Default.WithEmitMetadataOnly(true))

Da mesma forma, isso pode ser observado ao usar o argumento de linha de comando /refonly ou a propriedade ProduceOnlyReferenceAssembly MSBuild.

partial não pode ser o tipo de retorno de métodos

Introduzido no Visual Studio 2022 versão 17.14

Os recursos de linguagem de eventos e construtores parciais permite o modificador partial em mais lugares e, portanto, não pode ser um tipo de retorno, a menos que seja escapado:

class C
{
    partial F() => new partial(); // previously worked
    @partial F() => new partial(); // workaround
}

class partial { }

extension tratada como uma palavra-chave contextual

Introduzido no Visual Studio 2022 versão 17.14. A partir do C# 14, a extension palavra-chave serve a uma finalidade especial na denotação de contêineres de extensão. Isso altera a forma como o compilador interpreta determinados constructos de código.

Se você precisar usar "extensão" como um identificador em vez de uma palavra-chave, utilize o prefixo @ para escapá-lo: @extension. Isso informa ao compilador para tratá-lo como um identificador regular em vez de uma palavra-chave.

O compilador analisará isso como um contêiner de extensão em vez de um construtor.

class @extension
{
    extension(object o) { } // parsed as an extension container
}

O compilador não analisará isso como um método com o tipo extensionde retorno.

class @extension
{
    extension M() { } // will not compile
}

Introduzido no Visual Studio 2026 versão 18.0. O identificador de "extensão" pode não ser usado como um nome de tipo, portanto, o seguinte não será compilado:

using extension = ...; // alias may not be named "extension"
class extension { } // type may not be named "extension"
class C<extension> { } // type parameter may not be named "extension"

Propriedades e eventos parciais agora são implicitamente virtuais e públicos

Introduzido no Visual Studio 2026 versão 18.0 versão prévia 1

Corrigimos uma inconsistência em que as propriedades e eventos parciais da interface não seriam implicitamente virtual e public ao contrário de seus equivalentes não parciais. No entanto, essa inconsistência é preservada para métodos de interface parciais para evitar uma alteração significativa maior. Observe que o Visual Basic e outras linguagens que não dão suporte a membros de interface padrão começarão a exigir a implementação de membros de interface implicitamente virtuais partial .

Para manter o comportamento anterior, marque explicitamente os membros da interface partial como private (se eles não tiverem modificadores de acessibilidade) e sealed (se eles não tiverem o modificador private que implica sealed, e eles ainda não tiverem o modificador virtual ou sealed).

System.Console.Write(((I)new C()).P); // wrote 1 previously, writes 2 now

partial interface I
{
    public partial int P { get; }
    public partial int P => 1; // implicitly virtual now
}

class C : I
{
    public int P => 2; // implements I.P
}
System.Console.Write(((I)new C()).P); // inaccessible previously, writes 1 now

partial interface I
{
    partial int P { get; } // implicitly public now
    partial int P => 1;
}

class C : I;

Em mais casos, ParamCollectionAttribute está ausente

Introduzido no Visual Studio 2026 versão 18.0

Se você estiver compilando um .netmodule (observe que isso não se aplica a compilações normais de DLL/EXE) e possuir um lambda ou uma função local com um parâmetro de coleção params, e o ParamCollectionAttribute não for encontrado, um erro de compilação será relatado agora (porque o atributo agora precisa ser emitido no método sintetizado, mas o tipo de atributo em si não é sintetizado pelo compilador em um .netmodule). Você pode contornar isso definindo o atributo por conta própria.

using System;
using System.Collections.Generic;
class C
{
    void M()
    {
        Func<IList<int>, int> lam = (params IList<int> xs) => xs.Count; // error if ParamCollectionAttribute does not exist
        lam([1, 2, 3]);

        int func(params IList<int> xs) => xs.Count; // error if ParamCollectionAttribute does not exist
        func(4, 5, 6);
    }
}