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: Jednoduché parametry lambda s modifikátory

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 ReadOnlySpan<T> přetížení jsou použitelná ve více případech v C# 14 a novějších verzí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;
}

Enumerable.Reverse

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

Upozornění

To má vliv jenom na zákazníky používající C# 14 a cílení na .NET starší než net10.0, což je nepodporovaná konfigurace.

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šen, což má odlišnou sémantiku než Enumerable.Reverse(this IEnumerable<T>) (což bylo vyřešeno ve verzích 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;
    }
}

Upozornění na redundantní vzor v jednoduchých or vzorech

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

V disjunktivním or vzoru, jako je is not null or 42 nebo is not int or string, je druhý vzor redundantní a pravděpodobně vyplývá z nedorozumění ohledně pořadí precedence kombinátorů vzorů not a or.
Kompilátor poskytuje upozornění v běžných případech této chyby:

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

Je pravděpodobné, že uživatel myslel is not (null or 42) nebo is not (int or string) místo toho.

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 povoleno 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 automaticky generované podpůrné pole pro danou vlastnost.

Upozornění CS9258 je hlášeno, když se identifikátor váže na jiný symbol ve verzi jazyka 13 či 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 automaticky generované podpůrné pole pro danou vlastnost.

Chyba CS9272 je hlášena, když je místní proměnná nebo parametr vnořené funkce s názvem field deklarován v akcesoru 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 metody 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.

partial nemůže být návratovým typem metod.

Představeno v sadě Visual Studio 2022 verze 17.14

Jazyková funkce částečných událostí a konstruktorů povoluje modifikátor partial na více místech, a proto nemůže být použita jako návratový typ, pokud není označený jako výjimka:

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

class partial { }

extension považováno za kontextové klíčové slovo

Představeno v sadě Visual Studio 2022 verze 17.14. Od verze C# 14 slouží klíčové slovo extension ke zvláštnímu účelu označování kontejnerů rozšíření. Tím se změní způsob interpretace určitých konstruktorů kódu kompilátoru.

Pokud potřebujete místo klíčového slova použít "extension" jako identifikátor, použijte předponu @: @extension. To kompilátoru říká, aby s ním místo klíčového slova zacházeli jako s běžným identifikátorem.

Kompilátor to parsuje jako kontejner rozšíření místo konstruktoru.

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

Kompilátoru se toto nepodaří analyzovat jako metodu s návratovým typem extension.

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

Představeno v sadě Visual Studio 2026 verze 18.0. Identifikátor "extension" nelze použít jako název typu, takže se následující kód nezkompiluje:

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"

Částečné vlastnosti a události jsou teď implicitně virtuální a veřejné.

Představeno v sadě Visual Studio 2026 verze 18.0 Preview 1

Opravili jsme nekonzistence , kdy částečné vlastnosti rozhraní a události nebyly implicitně virtual a public na rozdíl od jejich nečástečně ekvivalentních. Tato nekonzistence je však zachována pro částečné metody rozhraní, aby se zabránilo větší zásadní změně. Všimněte si, že Visual Basic a další jazyky, které nepodporují výchozí členy rozhraní, začnou vyžadovat implementaci implicitně virtuálních partial členů rozhraní.

Pokud chcete zachovat předchozí chování, explicitně označte partial členy rozhraní jako private (pokud nemají žádné modifikátory přístupnosti) a sealed (pokud nemají private modifikátor, který znamená sealed, a nemají modifikátor virtual nebo 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;

Chybějící ParamCollectionAttribute je hlášeno ve více případech.

Představeno ve Visual Studio 2026 verze 18.0

Pokud kompilujete .netmodule (všimněte si, že to neplatí pro normální kompilace DLL/EXE) a máte lambda a místní funkci s parametrem params kolekce a ParamCollectionAttribute není nalezena, je nyní hlášena chyba kompilace (protože atribut nyní musí být vygenerován na syntetizovanou metodu, ale samotný typ atributu není kompilátorem syntetizován do .netmodule). Tento problém můžete obejít definováním atributu sami.

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