次の方法で共有


.NET 9.0.100 から .NET 10.0.100 以降の Roslyn での破壊的変更

このドキュメントでは、.NET 9 一般リリース (.NET SDK バージョン 9.0.100) から .NET 10 一般リリース (.NET SDK バージョン 10.0.100) までの Roslyn での既知の破壊的変更を示します。

ラムダ パラメーター リスト内の scoped は常に修飾子と見なされる

Visual Studio 2022 バージョン 17.13 で導入

C# 14 では、パラメーターの型を指定せずに、パラメーター修飾子を使用してラムダを記述する機能が導入されました: https://github.com/dotnet/csharplang/blob/main/proposals/simple-lambda-parameters-with-modifiers.md

この作業の一環として、以前は型名として受け入れられていた場合でも、scoped を常にラムダ パラメーターの修飾子として扱うという重大な変更が行われました。 次に例を示します。

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 では、C# 13 では Span<T> が実装するインターフェイスを受け取るオーバーロード (例: 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);
}

そのため、ReadOnlySpan<T> は一般に、C# 14 でのオーバーロード解決によって 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 Framework より古い .NET を System.Memory 参照でターゲットにしている場合、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 で導入

新しい Roslyn コンパイラ バージョンによってコンパイルされたコードには、UnscopedRefAttribute が、以前の ref 安全規則のコンテキスト (すなわち、net6.0 以前で C# 10 以前を対象)でコンパイルされた場合でも、意図せず影響を及ぼしました。 ただし、この属性はそのコンテキストで効果を持つべきではないため、現在は修正されています。

以前に net6.0 以前で C# 10 以前のエラーを報告しなかったコードは、現在コンパイルに失敗する可能性があります。

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 安全規則でコンパイルされているため実際には効果があると考える)、この属性が net6.0 以前の C# 10 以前で使用されている場合に警告が報告されます。

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. クラスである必要があります
  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 は、プロパティ アクセサー内で使用される場合、プロパティの合成バッキング フィールドを参照します。

警告 CS9258 は、識別子が言語バージョン 13 以前の別のシンボルにバインドされていた場合に報告されます。

合成バッキング フィールドの生成を回避し、既存のメンバーを参照するには、代わりに '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 は、プロパティ アクセサー内で使用される場合、プロパティの合成バッキング フィールドを参照します。

エラー CS9272 は、プロパティ アクセサーでローカル変数または入れ子になった関数のパラメーターが field という名前で宣言されたときに報告されます。

このエラーを回避するには、変数の名前を変更するか、宣言で @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;
        }
    }
}

record 型および record struct 型は、独自のEquals実装を提供する場合でも、ポインター型のメンバーを定義できません

Visual Studio 2022 バージョン 17.14 で導入

record class 型と record struct 型の仕様では、インスタンス フィールドとしてポインター型が許可されていないことを示していました。 ただし、record class 型または record 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 または ProduceOnlyReferenceAssembly MSBuild プロパティを使用する場合に確認できます。