Udostępnij przez


Przełomowe zmiany w Roslyn od wersji .NET 9.0.100 do wersji .NET 10.0.100

Ten dokument zawiera listę znanych zmian powodujących niekompatybilność w Roslyn po ogólnym wydaniu .NET 9 (zestaw .NET SDK w wersji 9.0.100) do ogólnego wydania .NET 10 (zestaw .NET SDK w wersji 10.0.100).

scoped na liście parametrów lambda jest teraz zawsze modyfikatorem.

wprowadzone w programie Visual Studio 2022 w wersji 17.13

C# 14 wprowadza możliwość zapisu lambda z modyfikatorami parametrów bez konieczności określania typu parametru: Proste parametry lambda z modyfikatorami

W ramach tego projektu zaakceptowano przełomową zmianę, zgodnie z którą scoped zawsze będzie traktowana jako modyfikator w parametrze lambda, nawet jeśli w przeszłości mogła być uznawana za nazwę typu. Na przykład:

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

ref struct @scoped { }

W języku C# 14 będzie to błąd, ponieważ oba tokeny scoped są traktowane jako modyfikatory. Obejście polega na użyciu @ w pozycji nazwy typu w następujący sposób:

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

ref struct @scoped { }

przeciążenia Span<T> i ReadOnlySpan<T> są stosowane w większej liczbie scenariuszy w C# 14 i nowszych

wprowadzone w programie Visual Studio 2022 w wersji 17.13

Język C# 14 wprowadza nowe wbudowane konwersje span i reguły wnioskowania typów. Oznacza to, że można wybrać różne przeciążenia w porównaniu z C# 13, a czasami może zostać zgłoszony niejednoznaczny błąd czasu kompilacji, ponieważ nowe przeciążenie ma zastosowanie, ale nie ma jednego najlepszego przeciążenia.

W poniższym przykładzie przedstawiono pewne niejednoznaczności i możliwe obejścia. Należy pamiętać, że innym obejściem jest użycie OverloadResolutionPriorityAttributeprzez autorów interfejsów API.

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

W języku C# 14 można wybrać przeciążenie Span<T>, gdzie w języku C# 13 wybrano przeciążenie przyjmujące interfejs implementowany przez T[] (na przykład IEnumerable<T>), co może prowadzić do ArrayTypeMismatchException w czasie wykonywania, jeśli jest używane z kowariantną tablicą.

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

Dlatego ReadOnlySpan<T> jest zazwyczaj preferowane nad Span<T> przez rozwiązanie przeciążenia w języku C# 14. W niektórych przypadkach może to prowadzić do przerwania kompilacji, na przykład gdy istnieją przeciążenia zarówno dla Span<T>, jak i ReadOnlySpan<T>, które przyjmują i zwracają ten sam typ zakresu.

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

W przypadku korzystania z języka C# 14 lub nowszego i określania wartości docelowej platformy .NET starszej niż net10.0 lub .NET Framework z System.Memory odwołaniem nastąpiła zmiana powodująca niezgodność z tablicami Enumerable.Reverse i .

Ostrzeżenie

Ma to wpływ tylko na klientów korzystających z języka C# 14 i przeznaczonych dla platformy .NET wcześniej niż net10.0, co jest nieobsługiwaną konfiguracją.

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

Na net10.0znajduje się Enumerable.Reverse(this T[]), która ma pierwszeństwo, dlatego unika się przerwania. W przeciwnym razie MemoryExtensions.Reverse(this Span<T>) jest rozwiązywany, co ma inne znaczenie semantyczne niż Enumerable.Reverse(this IEnumerable<T>) (który był rozwiązywany w C# 13 i poniżej). W szczególności rozszerzenie Span wykonuje odwrócenie i zwraca void. Aby obejść ten problem, można zdefiniować własne Enumerable.Reverse(this T[]) lub jawnie użyć Enumerable.Reverse:

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

Diagnostyka jest teraz raportowana dla metody usuwania opartej na wzorcu w foreach

wprowadzone w programie Visual Studio 2022 w wersji 17.13

Na przykład przestarzała metoda DisposeAsync jest teraz zgłaszana w 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;
    }
}

Ustaw stan obiektu wyliczającego na "po" podczas usuwania

wprowadzone w programie Visual Studio 2022 w wersji 17.13

Maszyna stanu dla modułów wyliczających nieprawidłowo zezwalała na wznawianie wykonywania po usunięciu modułu wyliczającego.
Teraz w usuniętym wyliczaczu MoveNext() prawidłowo zwraca false bez wykonywania dalszego kodu użytkownika.

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

Ostrzegaj o nadmiarowym wzorcu w prostych or schematach

wprowadzone w programie Visual Studio 2022 w wersji 17.13

W deseniu rozłącznym or, takim jak is not null or 42 lub is not int or string, drugi wzorzec jest nadmiarowy i prawdopodobnie wynika z nieporozumień co do kolejności priorytetów i kombinatorów wzorców dla not i or.
Kompilator udostępnia ostrzeżenie w typowych przypadkach tego błędu:

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

Prawdopodobnie użytkownik miał na myśli is not (null or 42) lub is not (int or string) zamiast tego.

UnscopedRefAttribute nie można używać ze starymi regułami bezpieczeństwa ref

wprowadzone w programie Visual Studio 2022 w wersji 17.13

Kod kompilowany przez nowe wersje kompilatora Roslyn został niezamierzenie dotknięty przez UnscopedRefAttribute, nawet gdy kod był kompilowany w kontekście wcześniejszych reguł bezpieczeństwa ref (tj. przeznaczony dla C# 10 lub starszej z net6.0 lub starszym). Jednak atrybut nie powinien mieć wpływu w tym kontekście i to jest teraz naprawione.

Kod, który wcześniej nie zgłosił żadnych błędów w języku C# 10 lub starszym z programem net6.0 lub starszym, może teraz zakończyć się niepowodzeniem kompilacji:

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

Aby zapobiec nieporozumieniu (myślenie, że atrybut ma efekt, ale w rzeczywistości nie dlatego, że kod jest kompilowany z wcześniejszymi regułami bezpieczeństwa ref), ostrzeżenie jest zgłaszane, gdy atrybut jest używany w języku C# 10 lub starszym z net6.0 lub starszym:

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 jest weryfikowane podczas deklaracji

wprowadzone w programie Visual Studio 2022 w wersji 17.13

Kompilator sprawdza teraz kształt Microsoft.CodeAnalysis.EmbeddedAttribute po zadeklarowaniu w źródle. Wcześniej kompilator zezwalał na deklaracje atrybutu definiowane przez użytkownika, ale tylko wtedy, gdy nie musiał generować go sam. Teraz sprawdzamy, czy:

  1. Musi być wewnętrzny
  2. Musi być klasą
  3. Musi być zapieczętowany
  4. Musi być niestatyczny
  5. Musi mieć wewnętrzny lub publiczny konstruktor bez parametrów
  6. Musi dziedziczyć z elementu System.Attribute.
  7. Należy to dopuścić w przypadku każdej deklaracji typu (klasa, struktura, interfejs, wyliczenie lub delegat)
namespace Microsoft.CodeAnalysis;

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

Wyrażenie field w akcesorze właściwości odnosi się do syntetyzowanego pola wspierającego

wprowadzone w programie Visual Studio 2022 w wersji 17.12

Wyrażenie field, gdy jest używane w akcesorze właściwości, odnosi się do syntetyzowanego pola pomocniczego dla właściwości.

Ostrzeżenie CS9258 jest zgłaszane, gdy identyfikator zostałby powiązany z innym symbolem w wersji języka 13 lub wcześniejszej.

Aby uniknąć generowania zsyntetyzowanego pola zapasowego i odwołać się do istniejącego pola członkowskiego, użyj 'this.field' lub '@field'. Alternatywnie zmień nazwę istniejącego elementu i odwołanie do tego elementu, aby uniknąć konfliktu z 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;
        }
    }
}

Zmienna o nazwie field jest niedozwolona w akcesorze właściwości

wprowadzone w programie Visual Studio 2022 w wersji 17.14

Wyrażenie field, gdy jest używane w akcesorze właściwości, odnosi się do syntetyzowanego pola pomocniczego dla właściwości.

Błąd CS9272 jest zgłaszany, gdy lokalna zmienna lub parametr funkcji zagnieżdżonej o nazwie field jest zadeklarowany w akcesorze właściwości.

Aby uniknąć błędu, zmień nazwę zmiennej lub użyj @field w deklaracji .

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

Typy record i record struct nie mogą definiować elementów członkowskich typu wskaźnika, nawet jeśli przedstawią własne implementacje Equals.

wprowadzone w programie Visual Studio 2022 w wersji 17.14

Specyfikacja typów record class i record struct wskazuje, że wszystkie typy wskaźników nie mogą być używane jako pola wystąpienia. Nie zostało to jednak wymuszone poprawnie, gdy typ record class lub record struct zdefiniował własną implementację Equals.

Kompilator teraz poprawnie to zabrania.

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

Emitowanie plików wykonywalnych zawierających tylko same metadane wymaga punktu wejścia

wprowadzone w programie Visual Studio 2022 w wersji 17.14

Wcześniej punkt wejścia był przypadkowo nieustawiony podczas emisji plików wykonywalnych w trybie tylko metadanych (nazywanym również zestawami referencyjnymi). Jest to teraz poprawione, ale oznacza to również, że brakujący punkt wejścia jest błędem kompilacji:

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

Podobnie można to zaobserwować podczas używania argumentu wiersza polecenia /refonly lub właściwości ProduceOnlyReferenceAssembly MSBuild.

partial nie może być zwracanym typem metod

wprowadzone w programie Visual Studio 2022 w wersji 17.14

Funkcja języka częściowych zdarzeń i konstruktorów umożliwia użycie modyfikatora partial w większej liczbie miejsc, dlatego nie może być używany jako typ zwracany, chyba że zostanie odpowiednio ucieczony.

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

class partial { }

extension traktowane jako słowo kluczowe kontekstowe

Wprowadzono w programie Visual Studio 2022 w wersji 17.14. Począwszy od języka C# 14, extension słowo kluczowe służy do specjalnego przeznaczenia w oznaczaniu kontenerów rozszerzeń. Zmienia to sposób, w jaki kompilator interpretuje niektóre konstrukcje kodu.

Jeśli musisz użyć "rozszerzenie" jako identyfikatora, a nie słowa kluczowego, poprzedź je prefiksem @: @extension. Dzięki temu kompilator traktuje go jako zwykły identyfikator zamiast słowa kluczowego.

Kompilator przeanalizuje to jako kontener rozszerzenia, a nie konstruktor.

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

Kompilator nie zdoła przeanalizować tego jako metody z typem zwracanym extension.

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

Wprowadzono w programie Visual Studio 2026 w wersji 18.0. Identyfikator "extension" może nie być używany jako nazwa typu, więc następujące polecenie nie zostanie skompilowane:

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"

Właściwości częściowe i zdarzenia są teraz niejawnie wirtualne i publiczne

Wprowadzono w programie Visual Studio 2026 w wersji 18.0 (wersja zapoznawcza 1)

Rozwiązaliśmy niespójność polegającą na tym, że częściowe właściwości interfejsu i zdarzenia nie byłyby niejawnie virtual i public w przeciwieństwie do ich częściowych odpowiedników. Ta niespójność jest jednak zachowywana w przypadku metod interfejsu częściowego, aby uniknąć większych zmian powodujących niezgodność. Należy pamiętać, że język Visual Basic i inne języki, które nie obsługują domyślnych elementów członkowskich interfejsu, zaczną implementować niejawnie wirtualne partial elementy członkowskie interfejsu.

Aby zachować poprzednie zachowanie, jawnie oznacz partial elementy członkowskie interfejsu jako private (jeśli nie mają żadnych modyfikatorów ułatwień dostępu) i sealed (jeśli nie mają private modyfikatora, co oznacza sealed, i nie mają jeszcze modyfikatora virtual ani 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;

Brak ParamCollectionAttribute jest zgłaszany w większej liczbie przypadków

Wprowadzono w programie Visual Studio 2026 w wersji 18.0

Jeśli kompilujesz element .netmodule (pamiętaj, że nie dotyczy to standardowych kompilacji DLL/EXE), i posiadasz funkcję lambda lub lokalną z parametrem kolekcji params, a ParamCollectionAttribute nie zostanie znaleziony, zgłoszony zostanie błąd kompilacji (ponieważ atrybut musi być teraz emitowany w syntetyzowanej metodzie, ale sam typ atrybutu nie jest syntetyzowany przez kompilator w obiekcie .netmodule). Możesz to obejść, definiując atrybut samodzielnie.

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