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.0
se 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:
- Musí být interní.
- Musí to být třída.
- Musí být zapečetěný.
- Musí být nestatický.
- Musí mít interní nebo veřejný konstruktor bez parametrů.
- Musí dědit ze System.Attribute.
- 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.
Roslyn breaking changes