Modifiche di rilievo in Roslyn dopo .NET da 10.0.100 a .NET 11.0.100

Questo documento elenca le modifiche di rilievo note in Roslyn dopo .NET versione generale 10 (.NET SDK versione 10.0.100) fino a .NET versione generale 11 (.NET SDK versione 11.0.100).

Il contesto sicuro di un'espressione di raccolta di tipo Span/ReadOnlySpan è ora di tipo declaration-block

Introdotto in Visual Studio 2026 versione 18.3

Il compilatore C# ha apportato una modifica importante per rispettare correttamente le regole di sicurezza dei riferimenti nella specifica della funzionalità delle espressioni di raccolta. In particolare, la clausola seguente:

  • Se il tipo di destinazione è un tipo spanSystem.Span<T> o System.ReadOnlySpan<T>, il contesto sicuro per l'espressione di raccolta è il blocco di dichiarazione.

In precedenza, il compilatore usava il membro della funzione safe-context in questa situazione. È stata apportata una modifica per usare il blocco di dichiarazione in base alla specifica. Ciò può causare la visualizzazione di nuovi errori nel codice esistente, ad esempio nello scenario seguente:

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 il codice è interessato da questo cambiamento radicale, considerare di utilizzare un tipo di array per le espressioni di raccolta pertinenti.

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
}

In alternativa, spostare l'espressione di raccolta in un ambito in cui è consentita l'assegnazione:

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
}

Vedi anche https://github.com/dotnet/csharplang/issues/9750.

Gli scenari che richiedono al compilatore di sintetizzare un delegato di ritorno ref readonly richiedono ora la disponibilità del tipo System.Runtime.InteropServices.InAttribute.

Introdotto in Visual Studio 2026 versione 18.3

Il compilatore C# ha apportato una modifica di rilievo per generare correttamente i metadati per ref readonly che restituiscono delegati sintetizzati

Ciò può causare la visualizzazione di un "errore CS0518: il tipo predefinito 'System.Runtime.InteropServices.InAttribute' non è definito o importato" nel codice esistente, ad esempio negli scenari seguenti:

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

Se il codice è interessato da questo cambiamento incompatibile, è consigliabile aggiungere un riferimento a un assembly che definisce System.Runtime.InteropServices.InAttribute nel tuo progetto.

Gli scenari che usano ref readonly funzioni locali ora richiedono la disponibilità di tipo System.Runtime.InteropServices.InAttribute.

Introdotto in Visual Studio 2026 versione 18.3

Il compilatore C# ha apportato una modifica di rilievo per generare correttamente i metadati per le funzioni locali che restituiscono ref readonly.

Ciò può causare la visualizzazione di un "errore CS0518: il tipo predefinito 'System.Runtime.InteropServices.InAttribute' non è definito o importato" nel codice esistente, ad esempio nello scenario seguente:

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

Se il codice è interessato da questo cambiamento incompatibile, è consigliabile aggiungere un riferimento a un assembly che definisce System.Runtime.InteropServices.InAttribute nel tuo progetto.

La valutazione dinamica degli &&/|| operatori non è consentita con l'operando sinistro tipizzato in modo statico come interfaccia.

Introdotto in Visual Studio 2026 versione 18.3

Il compilatore C# segnala ora un errore quando un tipo di interfaccia viene usato come operando sinistro di un operatore logico && o || con un dynamic operando destro. In precedenza, il codice veniva compilato per un tipo di interfaccia con true/false operatori, ma falliva in fase di esecuzione con un RuntimeBinderException perché il binder di runtime non è in grado di richiamare gli operatori definiti sulle interfacce.

Questa modifica impedisce un errore di runtime segnalandolo in fase di compilazione. Il messaggio di errore è:

errore CS7083: l'espressione deve essere convertibile in modo implicito in booleano o il relativo tipo 'I1' non deve essere un'interfaccia e deve definire l'operatore '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 il codice è influenzato da questa modifica interrompente, è consigliabile modificare il tipo statico dell'operando sinistro da un tipo di interfaccia a un tipo di classe concreto o al 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
}

Vedi anche https://github.com/dotnet/roslyn/issues/80954.

nameof(this.) negli attributi non è consentito

Introdotto in Visual Studio 2026 versione 18.3 e .NET 10.0.200

L'uso della parola chiave this o base all'interno di nameof in un attributo era precedentemente consentito in Roslyn a partire da C# 12 e viene ora correttamente non consentito per corrispondere alla specifica del linguaggio. Questa modifica che causa un'interruzione può essere mitigata rimuovendo this. e accedendo al membro senza il qualificatore.

Vedi anche 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() { }
}

Analisi di 'with' all'interno di switch-expression-arm

Introdotto in Visual Studio 2026 versione 18.4

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

In precedenza, quando viene visualizzato quanto segue, il compilatore considera (X.Y)when come un'espressione cast. In particolare, eseguire il cast dell'identificatore contestuale when in (X.Y):

x switch
{
    (X.Y) when
}

Questo era indesiderato e ciò implicava che un semplice when controllo del modello (come (X.Y) when a > b =>) non analizzava correttamente. Ora, questo viene considerato come un modello (X.Y) costante seguito da un oggetto when clause.

with() è trattato come un elemento di espressione di raccolta negli argomenti di costruzione della raccolta

Introdotto in Visual Studio 2026 versione 18.4

with(...) è utilizzato come elemento in un'espressione di raccolta e, quando LangVersion è impostato su 15 o superiore, è legato come argomenti passati al costruttore o al metodo di fabbrica utilizzato per creare la raccolta, anziché come invocazione di un metodo denominato with.

Per eseguire l'associazione a un metodo denominato with, usare @with invece .

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) { ... }

I tipi di puntatore non richiedono più un contesto non sicuro

Introdotto in Visual Studio 2026 versione 18.7

In C# 16, i tipi di puntatore (ad esempio int*, delegate*<void>) non richiedono più un contesto non sicuro. Solo le operazioni di indirezione del puntatore (dereferenziazione, accesso ai membri tramite ->, accesso agli elementi e così via) richiedono l'uso di unsafe. Questa è parte della funzionalità di evoluzione non sicura .

Poiché i tipi di puntatore sono ora legali in contesti sicuri, la risoluzione dell'overload può considerare candidati che in precedenza erano esclusi. Ciò può causare nuovi errori di ambiguità:

using System;

class Program
{
    static void Main()
    {
        M(x => { }); // C# 15: prints "2"; C# 16: 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);

In precedenza, l'espressione lambda x => { } non era in grado di eseguire la conversione F1 in in un contesto sicuro perché int* richiedeva un contesto non sicuro, quindi era applicabile solo M(F2) . Ora che int* è legale in contesti sicuri, l'espressione lambda è convertibile in entrambi i delegati, generando un errore di ambiguità.

Se il codice è interessato dalla modifica dell'ambiguità, aggiungere tipi di parametri espliciti all'espressione lambda per evitare ambiguità:

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

safe è una parola chiave contestuale

Introdotto in Visual Studio 2026 versione 18.9

In C# 16 safe è una parola chiave quando viene inserita come modificatore nelle dichiarazioni dei membri. Ciò può interrompere i casi in cui in precedenza si riferiva a un tipo. Per attenuare l'interruzione, è possibile usare @.

class safe { }

class C
{
    safe M1() => new safe(); // previously `safe` refers to a type, now it is a keyword
    @safe M2() => new safe(); // workaround
}

unsafe obbligatorio per più membri

Introdotto in Visual Studio 2026 versione 18.9

In precedenza, il compilatore C# non segnalava errori per chiamare alcuni membri con puntatori anche all'esterno del unsafe contesto. Questo problema è stato risolto in langversion:16. Per esempio:

var c = new C();
int a = c.M(null); // error always
int b = c[null]; // no error in C# 15, reports CS9363 in C# 16

class C
{
    public unsafe int M(int* x) => 0;
    public unsafe int this[int* x] => 0;
}

Per correggere le violazioni, usare il blocco o l'espressione unsafe , ad esempio:

int b = unsafe(c[null]);

closed è una parola chiave contestuale nei contesti di dichiarazione di tipo

Introdotto in Visual Studio 2026 versione 18.10

In C# 15, una dichiarazione di tipo o alias denominata closed senza un @ escape produce CS9380. Nei contesti di dichiarazione membro viene closed considerato come modificatore, quindi il codice usato closed in precedenza come nome di tipo può ora essere analizzato come dichiarazione incompleta e produrre CS1519.

Per continuare a usare closed come identificatore, eseguire l'escape della dichiarazione e dei riferimenti con @.

class @closed { }

class C
{
    closed oldField;      // C# 14: field of type 'closed'; C# 15: parsed as an incomplete declaration
    @closed currentField; // field of type 'closed'
}

union è una parola chiave contestuale nei contesti di dichiarazione di tipo

Introdotto in Visual Studio 2026 versione 18.10

In C# 15, union seguito da un nome di tipo, può essere analizzato come dichiarazione di unione. Il codice usato union in precedenza come nome di tipo in una dichiarazione può quindi produrre CS9370 anziché dichiarare un campo.

Per continuare a usare union come identificatore, eseguire l'escape con @.

class @union { }

class C
{
    union OldField;      // C# 14: field of type 'union'; C# 15: union declaration
    @union CurrentField; // field of type 'union'
}