Freigeben über


Bruch von Änderungen in Roslyn nach .NET 10.0.100 bis .NET 11.0.100

In diesem Dokument werden bekannte änderungen in Roslyn nach .NET 10 general release (.NET SDK Version 10.0.100) bis .NET 11 general release (.NET SDK Version 11.0.100) aufgeführt.

Der sichere Kontext eines Sammlungsausdrucks vom Typ Span/ReadOnlySpan ist jetzt declaration-block

Introduced in Visual Studio 2026, Version 18.3

Der C#-Compiler hat eine umfassende Änderung vorgenommen, um die Verweissicherheitsregeln in der Funktionsspezifikation der Auflistungsausdrücke einzuhalten. Insbesondere die folgende Klausel:

  • Wenn der Zieltyp ein span-TypSystem.Span<T> oder System.ReadOnlySpan<T>ist, ist der sichere Kontext des Sammlungsausdrucks der Deklarationsblock.

Zuvor hat der Compiler in dieser Situation den safe-context-function-member verwendet. Wir haben jetzt eine Änderung vorgenommen, um Deklarationsblock pro Spezifikation zu verwenden. Dies kann dazu führen, dass neue Fehler im vorhandenen Code angezeigt werden, z. B. im folgenden Szenario:

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
}

Wenn ihr Code von dieser unterbrechungsbezogenen Änderung betroffen ist, sollten Sie stattdessen einen Arraytyp für die relevanten Sammlungsausdrücke verwenden:

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
}

Alternativ können Sie den Auflistungsausdruck in einen Bereich verschieben, in dem die Zuordnung zulässig ist:

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
}

Siehe auch https://github.com/dotnet/csharplang/issues/9750.

Szenarien, die einen Compiler zum Synthetisieren eines Delegaten mit Rückgabewert ref readonly benötigen, setzen nun die Verfügbarkeit des Typs System.Runtime.InteropServices.InAttribute voraus.

Introduced in Visual Studio 2026, Version 18.3

Der C#-Compiler hat eine inkompatible Änderung vorgenommen, um Metadaten für die ordnungsgemäße Ausgabe von ref readonly synthetisierten Delegaten bei der Rückgabe zu erzeugen.

Dies kann dazu führen, dass ein Fehler CS0518: Vordefinierter Typ "System.Runtime.InteropServices.InAttribute" nicht definiert oder importiert wird" im vorhandenen Code angezeigt wird, z. B. in den folgenden Szenarien:

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

Wenn Ihr Code von diesem Breaking Change betroffen ist, sollten Sie in Erwägung ziehen, einen Verweis auf eine Assembly, die System.Runtime.InteropServices.InAttribute definiert, zu Ihrem Projekt hinzuzufügen.

Szenarien, die ref readonly lokale Funktionen verwenden, erfordern jetzt die Verfügbarkeit des Typs System.Runtime.InteropServices.InAttribute .

Introduced in Visual Studio 2026, Version 18.3

Der C#-Compiler hat eine bahnbrechende Änderung vorgenommen, um Metadaten für die Rückgabe lokaler Funktionen in ref readonly ordnungsgemäß auszugeben.

Dies kann dazu führen, dass ein Fehler CS0518: Vordefinierter Typ "System.Runtime.InteropServices.InAttribute" nicht definiert oder importiert wird" im vorhandenen Code angezeigt wird, z. B. im folgenden Szenario:

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

Wenn Ihr Code von diesem Breaking Change betroffen ist, sollten Sie in Erwägung ziehen, einen Verweis auf eine Assembly, die System.Runtime.InteropServices.InAttribute definiert, zu Ihrem Projekt hinzuzufügen.

Dynamische Auswertung von &&/|| Operatoren ist nicht zulässig, wenn der linke Operand statisch als Schnittstelle eingegeben wird.

Introduced in Visual Studio 2026, Version 18.3

Der C#-Compiler meldet jetzt einen Fehler, wenn ein Schnittstellentyp als linker Operand eines logischen && Oder || Operators mit einem dynamic rechten Operanden verwendet wird. Bisher würde Code für einen Schnittstellentyp mit true/false Operatoren kompiliert, schlägt jedoch zur Laufzeit mit einem RuntimeBinderException fehl, da der Laufzeitbinder keine Operatoren aufrufen kann, die für Schnittstellen definiert sind.

Diese Änderung verhindert einen Laufzeitfehler, indem er stattdessen zur Kompilierungszeit gemeldet wird. Die Fehlermeldung lautet:

fehler CS7083: Ausdruck muss implizit in boolean konvertierbar sein, oder der Typ "I1" darf keine Schnittstelle sein und muss den Operator 'false' definieren.

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'.
}

Wenn Ihr Code durch diese inkompatible Änderung betroffen ist, sollten Sie den statischen Typ des linken Operanden von einem Schnittstellentyp zu einem konkreten Klassentyp oder zu einem dynamic Typ ändern.

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
}

Siehe auch https://github.com/dotnet/roslyn/issues/80954.

nameof(this.) in Attributen ist unzulässig

Introduced in Visual Studio 2026, Version 18.3 und .NET 10.0.200

Die Verwendung des this oder base Schlüsselworts innerhalb eines Attributs in nameof war zuvor seit C# 12 in Roslyn unbeabsichtigt erlaubt und ist jetzt ordnungsgemäß nicht mehr zulässig, um der Sprachspezifikation zu entsprechen. Diese unterbrechungslose Änderung kann durch Entfernen this. und Zugreifen auf das Mitglied ohne Qualifizierer verringert werden.

Siehe auch 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() { }
}

Analysieren von "with" in einem Switch-Expression-Arm

Introduced in Visual Studio 2026 Version 18.4

Siehe https://github.com/dotnet/roslyn/issues/81837 und https://github.com/dotnet/roslyn/pull/81863

Zuvor behandelte der Compiler (X.Y)when, wenn er das Folgende sah, als Umwandlungsausdruck. Umwandeln des Kontextbezeichners when in (X.Y):

x switch
{
    (X.Y) when
}

Dies war unerwünscht und bedeutete, dass eine einfache when Überprüfung des Musters (wie (X.Y) when a > b =>) nicht ordnungsgemäß analysiert würde. Dies wird nun als konstantes Muster (X.Y) behandelt, gefolgt von einem when clause.

Ein with() als Sammlungsausdruckselement wird als Sammlungskonstruktionsargumente behandelt.

Introduced in Visual Studio 2026 Version 18.4

with(...) wird als Element in einem Auflistungsausdruck verwendet und wenn die LangVersion auf 15 oder höher festgelegt ist, als Argumente gebunden, die an Konstruktor- oder Factorymethode übergeben werden, die zum Erstellen der Auflistung verwendet wird, und nicht als Aufrufausdruck einer Methode mit dem Namen with.

Um an eine Methode mit dem Namen with zu binden, verwenden Sie stattdessen @with.

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

Zeigertypen erfordern keinen unsicheren Kontext mehr

Introduced in Visual Studio 2026, Version 18.7

In einer zukünftigen C#-Version (derzeit in langversion:preview) benötigen Zeigertypen (z. B. int*, delegate*<void>) keinen unsicheren Kontext mehr. Nur Zeigerindirektionsoperationen (Dereferenzierung, Memberzugriff über ->, Elementzugriff usw.) benötigen 'unsafe'. Dies ist Teil der Funktion unsichere Evolution.

Da Zeigertypen jetzt in sicheren Kontexten legal sind, kann die Überladungsauflösung Kandidaten in Betracht ziehen, die zuvor ausgeschlossen wurden. Dies kann zu neuen Mehrdeutigkeitsfehlern führen:

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);

Zuvor konnte die Lambda-Funktion x => { } nicht in F1 in einen sicheren Kontext konvertiert werden, da int* einen unsicheren Kontext erforderte, weshalb nur M(F2) anwendbar war. Da int* nun in sicheren Kontexten zulässig ist, ist der Lambda-Ausdruck in beide Delegaten konvertierbar, was einen Mehrdeutigkeitsfehler erzeugt.

Wenn Ihr Code von der Mehrdeutigkeitsänderung betroffen ist, fügen Sie der Lambda-Funktion explizite Parametertypen hinzu, um die Mehrdeutigkeit zu beseitigen.

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

** Für ein nicht initialisiertes stackalloc innerhalb von SkipLocalsInit ist ein unsicherer Codekontext erforderlich.

Introduced in Visual Studio 2026, Version 18.7

In einer zukünftigen C#-Version (derzeit in langversion:preview) erfordert ein stackalloc Ausdruck ohne Initialisierer innerhalb einer jetzt gekennzeichneten [SkipLocalsInit] Methode einen unsicheren Kontext, auch wenn der Zieltyp ist Span<T>. Dies liegt daran, dass SkipLocalsInit die Zero-Initialisierung des zugeordneten Speichers verhindert, wodurch es möglich wird, nicht initialisierte Daten zu lesen – ein Sicherheitsproblem bei der Speichersicherheit. Dies ist Teil der unsicheren Entwicklungsfunktion.

[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)
}

Wenn ihr Code betroffen ist, können Sie eine der folgenden Aktionen ausführen:

  • Fügen Sie einen unsafe-Block um das folgende stackalloc hinzu:
    [SkipLocalsInit]
    void M()
    {
        Span<int> a;
        unsafe { a = stackalloc int[5]; }
    }
    
  • Oder stellen Sie einen Initialisierer bereit, damit der Arbeitsspeicher vollständig initialisiert wird:
    [SkipLocalsInit]
    void M()
    {
        Span<int> a = stackalloc int[5] { 0, 0, 0, 0, 0 };
    }