Sdílet prostřednictvím


Zásadní změny v Roslynu po .NET 9.0.100 až .NET 10.0.100

Tento dokument uvádí známé zásadní změny v Roslynu po vydání obecné verze .NET 9 (.NET SDK verze 9.0.100) až po vydání obecné verze .NET 10 (.NET SDK verze 10.0.100).

scoped v seznamu parametrů lambda je teď vždy modifikátorem.

Představeno v prostředí Visual Studio 2022 verze 17.13

C# 14 zavádí možnost psát lambda s modifikátory parametrů, aniž by bylo nutné zadat typ parametru: https://github.com/dotnet/csharplang/blob/main/proposals/simple-lambda-parameters-with-modifiers.md

V rámci této práce byla přijata změna, která je považována za průlomovou, kde scoped bude vždy považován za modifikátor v parametru lambda, i když mohl být v minulosti přijat jako název typu. Například:

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

ref struct @scoped { }

V jazyce C# 14 se jedná o chybu, protože oba tokeny scoped se považují za modifikátory. Alternativním řešením je použít @ v pozici názvu typu, například takto:

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

ref struct @scoped { }

Span<T> a přetížení ReadOnlySpan<T> platí ve více scénářích v jazyce C# 14 a novějších

Představeno v prostředí Visual Studio 2022 verze 17.13

C# 14 zavádí nové integrované konverze rozsahu a pravidla odvození typů. To znamená, že v porovnání s C# 13 mohou být zvolena různá přetížení a někdy může dojít k chybě nejednoznačnosti při kompilaci, protože se stalo použitelné nové přetížení, ale neexistuje žádné nejvhodnější přetížení.

Následující příklad ukazuje několik nejednoznačností a možná alternativní řešení. Všimněte si, že dalším alternativním řešením je, aby autoři rozhraní API používali 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

V C# 14 může být zvoleno přetížení Span<T>, kde bylo v C# 13 vybráno přetížení, které přijímá rozhraní implementované T[] (například IEnumerable<T>), což může vést k ArrayTypeMismatchException během běhu programu, pokud se používá s kovariantním polem.

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

Z tohoto důvodu se ReadOnlySpan<T> obecně upřednostňuje před Span<T> díky řešení přetížení v jazyce C# 14. V některých případech to může vést k přerušením při kompilaci, například když existují přetížení pro obě Span<T> a ReadOnlySpan<T>, které přijímají a vracejí stejný typ rozsahu.

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

Pokud používáte C# 14 nebo novější a cílíte na .NET starší než net10.0 nebo na rozhraní .NET Framework s odkazem na System.Memory, dojde k zásadní změně týkající se Enumerable.Reverse a polí.

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

Na net10.0se nachází Enumerable.Reverse(this T[]), který má přednost, a proto se přerušení vyhneme. V opačném případě je MemoryExtensions.Reverse(this Span<T>) vyřešena, která má odlišnou sémantiku než Enumerable.Reverse(this IEnumerable<T>) (která byla vyřešena v C# 13 a nižších). Konkrétně rozšíření Span provede reverzaci na místě a vrátí void. Jako alternativní řešení můžete definovat vlastní Enumerable.Reverse(this T[]) nebo explicitně použít Enumerable.Reverse:

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

Diagnostika pro metodu likvidace založenou na vzorech v foreach jsou nyní hlášeny.

Představeno v prostředí Visual Studio 2022 verze 17.13

Například zastaralá metoda DisposeAsync se nyní uvádí v 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;
    }
}

Nastavení stavu objektu enumerátoru na "after" během likvidace

Představeno v prostředí Visual Studio 2022 verze 17.13

Stavový automat pro enumerátory nesprávně umožňoval pokračování v provádění po odstranění enumerátoru.
Nyní MoveNext() na uvolněném enumerátoru správně vrací false bez provádění jakéhokoli dalšího uživatelského kódu.

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

UnscopedRefAttribute nelze použít se starými pravidly bezpečnosti ref

Představeno v prostředí Visual Studio 2022 verze 17.13

UnscopedRefAttribute neúmyslně ovlivnil kód zkompilovaný novými verzemi kompilátoru Roslyn, přestože byl kód sestaven podle starších pravidel bezpečnosti referencí (tj. je zaměřen na C# 10 nebo starší a net6.0 nebo starší). Atribut by však neměl mít v tomto kontextu vliv, a to je nyní opraveno.

Kód, který dříve neoznamoval žádné chyby v jazyce C# 10 nebo starší s net6.0 nebo starším, se teď nepodaří zkompilovat:

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

Chcete-li zabránit nedorozumění (myslíte si, že atribut má účinek, ale ve skutečnosti ne, protože váš kód je zkompilován s dřívějšími pravidly bezpečnosti ref), zobrazí se upozornění při použití atributu v jazyce C# 10 nebo starším s net6.0 nebo starším:

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 se ověřuje při deklaraci.

Představeno v prostředí Visual Studio 2022 verze 17.13

Kompilátor teď ověří tvar Microsoft.CodeAnalysis.EmbeddedAttribute, když je deklarován ve zdroji. Dříve kompilátor umožňoval uživatelem definované deklarace tohoto atributu, ale pouze tehdy, když ho nemusel sám generovat. Teď ověříme, že:

  1. Musí být interní.
  2. Musí to být třída.
  3. Musí být zapečetěný.
  4. Musí být nestatický.
  5. Musí mít interní nebo veřejný konstruktor bez parametrů.
  6. Musí dědit ze System.Attribute.
  7. Musí být povolena pro jakoukoli deklaraci typu (třída, struktura, rozhraní, výčet nebo delegát).
namespace Microsoft.CodeAnalysis;

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

Výraz field v přístupovém objektu vlastnosti odkazuje na syntetizované záložní pole.

Představeno ve Visual Studio 2022 verze 17.12

Výraz field, pokud se používá v rámci přistupovače vlastnosti, odkazuje na syntetizované podpůrné pole pro danou vlastnost.

Upozornění CS9258 je hlášeno, když by identifikátor byl přiřazen k jinému symbolu ve verzi jazyka 13 nebo starší.

Abyste se vyhnuli generování syntetizovaného backingového pole a odkazování na existujícího člena, použijte místo toho "this.field" nebo "@field". Případně přejmenujte existujícího člena a odkaz na tento člen, aby nedošlo ke konfliktu s 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;
        }
    }
}

Proměnná s názvem field zakázána v přístupovém objektu vlastnosti

Představeno v sadě Visual Studio 2022 verze 17.14

Výraz field, pokud se používá v rámci přistupovače vlastnosti, odkazuje na syntetizované podpůrné pole pro danou vlastnost.

Chyba CS9272 je hlášena, když je místní nebo parametr vnořené funkce s názvem field deklarován v přístupovém objektu vlastnosti.

Pokud se chcete této chybě vyhnout, přejmenujte proměnnou nebo použijte @field v deklaraci.

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 a record struct nemohou definovat členy typu ukazatele, i když poskytují vlastní implementace Equals

Představeno v sadě Visual Studio 2022 verze 17.14

Specifikace pro typy record class a record struct uvádí, že všechny typy ukazatelů jsou zakázány jako instanční pole. To však nebylo správně vynuceno, když typ record class nebo record struct definoval vlastní implementaci Equals.

Kompilátor to teď správně zakázal.

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

Vytváření pouze metadatových spustitelných souborů vyžaduje vstupní bod.

Představeno v sadě Visual Studio 2022 verze 17.14

V minulosti byl vstupní bod neúmyslně zrušen při vytváření spustitelných souborů v režimu pouze metadat (také známý jako referenční sestavení). To je nyní opraveno, ale také to znamená, že chybějící vstupní bod je chyba kompilace:

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

Podobně to lze pozorovat při použití argumentu příkazového řádku /refonly nebo vlastnosti ProduceOnlyReferenceAssembly MSBuild.