本文件列出在 .NET 9 一般版本(.NET SDK 版本 9.0.100)到 .NET 10 一般版本(.NET SDK 版本 10.0.100)之間,Roslyn 的已知重大變更。

lambda 參數列表中的 scoped 現在一律是修飾詞。

Visual Studio 2022 17.13 版中引進

C# 14 引進了使用參數修飾詞撰寫 Lambda 的能力,而不需要指定參數類型: https://github.com/dotnet/csharplang/blob/main/proposals/simple-lambda-parameters-with-modifiers.md

在這項工作中,已接受一個重大變更,其中 scoped 將在 lambda 參數中一律被視為修飾詞,即使過去它可能被接受為類型名稱。 例如:

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

在 C# 14 中,這將會是錯誤,因為兩個 scoped 令牌都會被視為修飾詞。 因應措施是在類型名稱位置中使用 @,如下所示:

var v = (scoped @scoped s) => { ... };
ref struct @scoped { }
              Span<T>ReadOnlySpan<T> 多載在 C# 14 及更新版本中適用於更多情境。
              Visual Studio 2022 17.13 版中引進

C# 14 引進新的 內建範圍轉換和類型推斷規則。 這表示,相較於 C# 13,可能會選擇不同的多載。由於新的多載可用,但沒有單一最佳的多載,有時可能會引發模棱兩可的編譯時錯誤。

下列範例顯示一些模棱兩可和可能的因應措施。 請注意,另一個因應措施是讓 API 作者使用 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

在 C# 14 中,可能會選擇Span<T> 多載,而在 C# 13 中,選擇的則是采用由T[] 實作的介面的多載(例如 IEnumerable<T>)。如果與協變陣列一起使用,可能會在執行時導致ArrayTypeMismatchException

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

因此,在 C# 14 中,多載解析通常會較偏好選擇 ReadOnlySpan<T> 而非 Span<T>。 在某些情況下,這可能會導致編譯中斷,例如,當 Span<T>ReadOnlySpan<T>都有重載時,兩者都接收並返回相同的跨度類型:

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;

使用 C# 14 或更新版本,並以低於 net10.0 的 .NET 或含有 System.Memory 參考的 .NET Framework 為目標,當中 Enumerable.Reverse 和陣列會有重大變更:

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

net10.0上,有 Enumerable.Reverse(this T[]) 具有優先權,因此避免了中斷。 否則,會解析 MemoryExtensions.Reverse(this Span<T>),但其語意與 Enumerable.Reverse(this IEnumerable<T>) 不同(Enumerable.Reverse(this IEnumerable<T>) 曾經在 C# 13 和更低版本中被解析)。 具體來說,Span 擴展會在原地進行反轉,並傳回 void。 作為一個替代方案,您可以定義自己的 Enumerable.Reverse(this T[]) 或明確地使用 Enumerable.Reverse

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

現在會在 foreach 中報告模式化處置方法的診斷結果

Visual Studio 2022 17.13 版中引進

例如,現在會在 DisposeAsync中報告已過時的 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;

在處置期間將列舉值對象的狀態設定為 “after”

Visual Studio 2022 17.13 版中引進

列舉器的狀態機在列舉器被處置後錯誤地允許恢復執行。
現在,已處置列舉器上的 MoveNext() 會正確傳回 false,而不需要再執行任何用戶代碼。

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 不能與舊 ref 安全規則搭配使用
              Visual Studio 2022 17.13 版中引進

UnscopedRefAttribute 無意中影響了由新 Roslyn 編譯器版本編譯的程式碼,即使該程式碼是在舊版參考安全規則的情境下編譯的(例如,目標為 C# 10 或更早版本的 net6.0 或更早版本)。 不過,屬性不應該在該內容中產生效果,而且現在已修正。

先前在 C# 10 或更早版本以及 net6.0 或更早版本中未回報任何錯誤的程式碼現在可能無法編譯:

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;

若要避免誤解(認為屬性有效果,但實際上不是因為您的程序代碼是以先前的 ref 安全規則編譯),當屬性在 C# 10 或更早版本中搭配 net6.0 或更早版本使用時,就會報告警告:

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 在宣告時會被驗證
              Visual Studio 2022 17.13 版中引進

編譯程式現在會在來源中宣告時驗證 Microsoft.CodeAnalysis.EmbeddedAttribute 的形狀。 先前,編譯程式會允許使用者定義這個屬性的宣告,但前提是它自己不需要產生。 現在我們來驗證:

  • 它必須是內部的
  • 它必須是類別(class)
  • 它必須密封
  • 它必須是非靜態的
  • 它必須具有內部或公用無參數建構函式
  • 它必須繼承自 System.Attribute。
  • 任何類型宣告都必須允許它(類別、結構、介面、列舉或委派)
  • namespace Microsoft.CodeAnalysis;
    // Previously, sometimes allowed. Now, CS9271
    public class EmbeddedAttribute : Attribute {}
    

    屬性存取子中的表示式 field 是指自動生成的後援欄位

    Visual Studio 2022 17.12 版中引進

    該表達式 field 在屬性存取子內使用時,指的是該屬性的合成備援欄位。

    當標識符在語言版本 13 或更早版本中綁定至不同符號時,會出現警告 CS9258。

    若要避免產生合成支援字段,並參考現有的成員,請改用 'this.field' 或 '@field'。 或者,您可以重新命名現有的成員以及對該成員的引用,以避免與 field發生衝突。

    class MyClass
        private int field = 0;
        public object Property
                // 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;
    

    屬性存取子中不允許名為 field 的變數

    Visual Studio 2022 17.14 版中引進

    該表達式 field 在屬性存取子內使用時,指的是該屬性的合成備援欄位。

    如果在屬性存取子中宣告名稱為 field 的局部變數或巢狀函式的參數,則會報告錯誤 CS9272。

    若要避免錯誤,請重新命名變數,或使用 宣告中的 @field

    class MyClass
        public object Property
                // error CS9272: 'field' is a keyword within a property accessor.
                // Rename the variable or use the identifier '@field' instead.
                int field = 0;
                return @field;
    

    即使提供自己的 Equals 實作,recordrecord struct 類型也無法定義指標類型成員

    Visual Studio 2022 17.14 版中引進

    record classrecord struct 類型的規格表示不允許任何指標類型作為實例字段。 不過,當 record classrecord struct 類型定義自己的 Equals 實現時,未正確強制執行。

    編譯程式現在已正確禁止這種情況。

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

    要生成僅包含元數據的可執行檔,需要一個入口點。

    Visual Studio 2022 17.14 版中引進

    先前,當以僅限元數據模式(也稱為 ref 元件)生成可執行檔時,無意中未設定進入點。 現在已修正,但也表示遺漏的進入點是編譯錯誤:

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

    同樣地,當使用命令行自變數 /refonly 或 MSBuild 屬性 ProduceOnlyReferenceAssembly 時,也可以觀察到此情況。

    partial 不能是方法的傳回類型 Visual Studio 2022 17.14 版中引進

    部分事件和建構式語言功能允許在更多位置使用partial修飾詞,因此除非進行轉義,否則它不能作為傳回類型:

    class C
        partial F() => new partial(); // previously worked
        @partial F() => new partial(); // workaround
    class partial { }
                  extension 被視為內容關鍵詞
                  Visual Studio 2022 17.14 版中引進。 從 C# 14 開始, extension 關鍵詞會在表示擴充容器時提供特殊用途。
    這會變更編譯程式解譯特定程式代碼建構的方式。

    如果您需要使用 「extension」 作為識別碼而非關鍵詞,請使用 @ 前置詞逸出它: @extension。 這會告訴編譯程式將它視為一般標識符,而不是關鍵詞。

    編譯程式會將這個 剖析為擴充容器,而不是建構函式。

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

    編譯程式無法將這個 剖析為傳回型別 extension的方法。

    class @extension
        extension M() { } // will not compile
                  Visual Studio 2022 17.15 版中引進。 「延伸模組」識別碼可能無法作為類型名稱使用,因此下列專案將不會編譯:

    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"
    

    部分屬性和事件現在隱含為虛擬和公開

    Visual Studio 2022 17.15 版中引進

    我們已修正不一致,即部分介面的屬性和事件不會自動virtualpublic,這與其非部分的同類型不同。 不過,部分介面方法 會保留 此不一致的情況,以避免發生較大的中斷性變更。 請注意,Visual Basic 和其他不支援預設介面成員的語言將會開始需要實作隱含虛擬 partial 介面成員。

    若要保留先前的行為,請將介面成員明確標示為 partial(如果它們沒有任何存取修飾詞),以及 private(如果它們沒有 sealed,這意味著 private,並且它們尚未具有修飾詞 sealedvirtual)。

    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;