共用方式為


NET 9.0.100 版至 .NET 10.0.100 版後 Roslyn 的重大變更

本文件列出在 .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 的功能,而不需要指定參數類型: 具有修飾詞的簡單 lambda 參數

在這項工作中,已接受一個重大變更,其中 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;
}

Enumerable.Reverse

使用 C# 14 或更新版本,並以 .NET Framework net10.0System.Memory目標時,會使用 和 陣列進行Enumerable.Reverse重大變更。

謹慎

這只會影響使用 C# 14 並以早於 net10.0.NET 為目標的客戶,這是不支援的設定。

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

警告簡單 or 模式中的冗餘模式

Visual Studio 2022 17.13 版中引進

在析取模式如oris not null or 42中,第二個模式is not int or string是多餘的,這可能是由於誤解了模式組合器notor的優先順序所造成的。
編譯器會在此錯誤的常見情況下提供警告:

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

用戶很可能是指 is not (null or 42)is not (int or string)

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 的形狀。 先前,編譯程式會允許使用者定義這個屬性的宣告,但前提是它自己不需要產生。 現在我們來驗證:

  1. 它必須是內部的
  2. 它必須是類別(class)
  3. 它必須密封
  4. 它必須是非靜態的
  5. 它必須具有內部或公用無參數建構函式
  6. 它必須繼承自 System.Attribute。
  7. 任何類型宣告都必須允許它(類別、結構、介面、列舉或委派)
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
    {
        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;
        }
    }
}

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

Visual Studio 2022 17.14 版中引進

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

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

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

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

即使提供自己的 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 2026 18.0 版中引進。「擴充功能」識別碼不得用作類型名稱,因此不會編譯以下內容:

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 2026 18.0 版預覽版 1 中引進

我們已修正不一致,即部分介面的屬性和事件不會自動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;

失蹤 ParamCollectionAttribute 個案較多

在 Visual Studio 2026 18.0 版中引進

如果您正在編譯 .netmodule(請注意,這不適用於一般 DLL/EXE 編譯),而且具有集合參數的 params lambda 或本機函式,並且找不到 ParamCollectionAttribute,則現在會報告編譯錯誤(因為屬性現在必須在合成的方法上發出,但編譯器不會將屬性類型本身生成到 中)。 您可以自行定義屬性來解決此問題。

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