注
この記事は機能仕様です。 仕様は、機能の設計ドキュメントとして機能します。 これには、提案された仕様の変更と、機能の設計と開発時に必要な情報が含まれます。 これらの記事は、提案された仕様の変更が最終決定され、現在の ECMA 仕様に組み込まれるまで公開されます。
機能の仕様と完成した実装の間には、いくつかの違いがある可能性があります。 これらの違いは、関連する 言語設計会議 (LDM) ノートでキャプチャされます。
機能仕様を C# 言語標準に導入するプロセスの詳細については、仕様に関する記事を参照してください。
チャンピオン号: https://github.com/dotnet/csharplang/issues/8714
概要
新しい暗黙的な変換型を含む、言語での Span<T> と ReadOnlySpan<T> に対するファースト クラスのサポートを導入し、より多くの場所で検討し、これらの整数型を使用したより自然なプログラミングを可能にします。
モチベーション
C# 7.2 での導入以来、 Span<T> と ReadOnlySpan<T> は多くの重要な方法で言語および基底クラス ライブラリ (BCL) に取り組んでいます。 これは、開発者の安全性にコストをかけずにパフォーマンスを向上させるので、開発者にとって最適です。 ただし、この言語では、いくつかの重要な方法でこれらの型を arm の長さに保持しているため、API の意図を表現するのが困難になり、新しい API の表面領域の重複が大幅に発生します。 たとえば、BCL では .NET 9 で新しい tensor プリミティブ API が多数追加されましたが これらの API はすべて ReadOnlySpan<T>で提供されています。 C# は、 ReadOnlySpan<T>、 Span<T>、および T[]の関係を認識しないため、これらの型間にユーザー定義の変換がある場合でも、拡張メソッドレシーバーには使用できず、他のユーザー定義の変換で構成することもできず、すべてのジェネリック型推論シナリオでは役立ちません。
ユーザーは明示的な変換または型引数を使用する必要があります。つまり、IDE ツールは、変換後にこれらの型を渡すことが有効であることを IDE に示さないため、これらの API の使用をユーザーに指示しません。 このスタイルの API の使いやすさを最大限に高めるために、BCL では、Span<T> および T[] のオーバーロード全体のセットを定義する必要がありますが、これは実際には利益が得られない冗長な領域を維持することになります。 この提案では、言語でこれらの型と変換をより直接的に認識させることで、問題に対処することを目指しています。
たとえば、BCL は次のような任意の MemoryExtensions ヘルパーのオーバーロードを 1 つだけ追加できます。
int[] arr = [1, 2, 3];
Console.WriteLine(
arr.StartsWith(1) // CS8773 in C# 13, permitted with this proposal
);
public static class MemoryExtensions
{
public static bool StartsWith<T>(this ReadOnlySpan<T> span, T value) where T : IEquatable<T> => span.Length != 0 && EqualityComparer<T>.Default.Equals(span[0], value);
}
以前は、ユーザー定義の変換 (Span/array/ReadOnlySpan の間に存在する) は拡張レシーバーでは考慮されないため、Span/array 型の変数で拡張メソッドを使用できるようにするために、Span および配列のオーバーロードが必要でした。
詳細な設計
この提案の変更は、 LangVersion >= 14に関連付けられます。
スパン変換
§10.2.1 のリストに、暗黙的スパン変換という新しい型の暗黙的な変換を追加します。 この変換は型からの変換であり、次のように定義されます。
暗黙的なスパン変換を使用すると、次のように、 array_types、 System.Span<T>、 System.ReadOnlySpan<T>、および string を相互に変換できます。
- 要素型が
array_typeを持つ任意の 1 次元EiからSystem.Span<Ei> - 任意の要素型
array_typeを持つ1次元EiからSystem.ReadOnlySpan<Ui>に対して、Eiが§18.2.3.3でUiに共分散変換可能である場合 -
System.Span<Ti>からSystem.ReadOnlySpan<Ui>まで。ただし、Tiが に共分散変換可能であることが条件 (Ui) -
System.ReadOnlySpan<Ti>からSystem.ReadOnlySpan<Ui>まで。ただし、Tiが に共分散変換可能であることが条件 (Ui) -
stringからSystem.ReadOnlySpan<char>へ
Span/ReadOnlySpan 型は、 ref structであり、完全修飾名 (LDM 2024-06-24) で一致する場合、変換に適用可能と見なされます。
また、標準の暗黙的な変換 (§10.4.2) の一覧に単純スパン変換を追加します。 これにより、以前にリンクされた API 提案のように、引数解決を実行するときにオーバーロード解決でこれらを考慮できます。
明示的なスパン変換は次のとおりです。
- すべての暗黙的なスパン変換。
-
array_typeの要素型
TiからSystem.Span<Ui>またはSystem.ReadOnlySpan<Ui>へ明示的な参照変換が存在する場合、TiからUiへの変換ができる。
反対の標準の暗黙的な変換が常に存在する他の標準の明示的な変換 (§10.4.3) とは異なり、標準の明示的なスパン変換はありません。
ユーザー定義の変換
暗黙的または明示的なスパン変換が存在する型間で変換する場合、ユーザー定義の変換は考慮されません。
暗黙的なスパン変換は、非ユーザー定義変換が存在する型 (§10.5.2 許可されるユーザー定義変換) 間でユーザー定義演算子を定義できないという規則から除外されます。 これは、BCL が C# 14 に切り替えても既存の Span 変換演算子を定義し続けるために必要です (これらの演算子は、より低い LangVersion に必要であり、また、これらの演算子は新しい標準スパン変換の codegen で使用されるためです)。 ただし、実装の詳細として見ることができます (codegen と Lower LangVersions は仕様の一部ではありません)、Roslyn は仕様のこの部分に違反します (ユーザー定義の変換に関するこの特定の規則は適用されません)。
拡張レシーバー
また、適用性 (12.8.9.3) を決定するときに拡張メソッドの最初のパラメーターで許容される暗黙的な変換の一覧に単純スパン変換を追加します (太字で変更)。
拡張メソッド
Cᵢ.Mₑは、次の場合 対象の です。
Cᵢは、非ジェネリックで入れ子になっていないクラスですMₑの名前は、識別子ですMₑはアクセス可能であり、上記のように静的メソッドとして引数に適用する場合に適用できますexprから の最初のパラメータの型への暗黙的な ID、参照またはボックス化Mₑ変換。 スパン変換は、メソッド グループ変換に対してオーバーロード解決が実行されるときには考慮されません。
暗黙的なスパン変換は、メソッド グループ変換 (LDM 2024-07-15) の拡張レシーバーでは考慮されないことに注意してください。これにより、コンパイル時エラー CS1113: Extension method 'E.M<int>(Span<int>, int)' defined on value type 'Span<int>' cannot be used to create delegatesが発生するのではなく、次のコードが引き続き動作します。
using System;
using System.Collections.Generic;
Action<int> a = new int[0].M; // binds to M<int>(IEnumerable<int>, int)
static class E
{
public static void M<T>(this Span<T> s, T x) => Console.Write(1);
public static void M<T>(this IEnumerable<T> e, T x) => Console.Write(2);
}
可能な限り将来の作業では、メソッド グループ変換で拡張レシーバーに対してスパン変換が考慮されないというこの条件を削除することを検討し、代わりに変更を実装して、上記のようなシナリオで代わりに Span オーバーロードが正常に呼び出されるようにすることができます。
- コンパイラは、配列を受信側として受け取り、内部でスパン変換を実行するサンクを出力する可能性があります (
x => new int[0].M(x)のようなデリゲートを手動で作成するユーザーと同様)。 - 実装されている場合、値デリゲートは
Spanを受信側として直接受け取ることができます。
差異
スパン変換の単純分散セクションの目標はSystem.ReadOnlySpan<T>の共分散の量をレプリケートすることです。 ランタイムの変更は、ここでジェネリックを使用して分散を完全に実装するために必要です (「..ジェネリックで ref struct 型を使用するための /csharp-13.0/ref-struct-interfaces.md)。ただし、提案された .NET 9 API: https://github.com/dotnet/runtime/issues/96952を使用することで、限られた量の共分散を許可できます。 これにより、System.ReadOnlySpan<T>が一部のシナリオでTとして宣言されたかのように、言語でout Tを処理できるようになります。 ただ、この変異変換をすべての分散シナリオにわたり検証するのではなく、§18.2.3.3の分散変換可能の定義に追加しません。 将来、ここでの差異をより深く理解するようにランタイムを変更する場合は、小さな破壊的変更を行って言語で完全に認識することができます。
パターン
ref structを任意のパターンの型として使用する場合、ID 変換のみが許可されることに注意してください。
class C<T> where T : allows ref struct
{
void M1(T t) { if (t is T x) { } } // ok (T is T)
void M2(R r) { if (r is R x) { } } // ok (R is R)
void M3(T t) { if (t is R x) { } } // error (T is R)
void M4(R r) { if (r is T x) { } } // error (R is T)
}
ref struct R { }
is-type 演算子の仕様から (§12.12.12.1):
操作
E is T[...] の結果は、Eが null 以外であり、参照変換、ボックス化変換、ボックス化解除変換、ラップ解除変換、またはラップ解除変換によってT型に正常に変換できるかどうかを示すブール値です。[...]
Tが null 非許容値型の場合、trueとDが同じ型の場合、結果はTとなります。
この機能ではこの動作は変わらないため、 Span/ReadOnlySpanのパターンを記述することはできませんが、配列 (分散を含む) でも同様のパターンが可能です。
using System;
M1<object[]>(["0"]); // prints
M1<string[]>(["1"]); // prints
void M1<T>(T t)
{
if (t is object[] r) Console.WriteLine(r[0]); // ok
}
void M2<T>(T t) where T : allows ref struct
{
if (t is ReadOnlySpan<object> r) Console.WriteLine(r[0]); // error
}
コード生成
変換は、実装に使用されるランタイム ヘルパーが存在するかどうかに関係なく常に存在します (LDM 2024-05-13)。 ヘルパーが存在しない場合、変換を使用しようとすると、コンパイラが必要なメンバーが見つからないというコンパイル時エラーが発生します。
コンパイラは、次のヘルパーまたは同等のヘルパーを使用して変換を実装することを想定しています。
| 変換 | ヘルパー |
|---|---|
| 配列からスパンへ |
static implicit operator Span<T>(T[]) ( Span<T>で定義) |
| 配列を ReadOnlySpan に変換する |
static implicit operator ReadOnlySpan<T>(T[]) ( ReadOnlySpan<T>で定義) |
| スパンから ReadOnlySpan へ |
static implicit operator ReadOnlySpan<T>(Span<T>) ( Span<T>で定義)そして static ReadOnlySpan<T>.CastUp<TDerived>(ReadOnlySpan<TDerived>) |
| ReadOnlySpan から ReadOnlySpan へ | static ReadOnlySpan<T>.CastUp<TDerived>(ReadOnlySpan<TDerived>) |
| 文字列から ReadOnlySpan へ | static ReadOnlySpan<char> MemoryExtensions.AsSpan(string) |
MemoryExtensions.AsSpanは、stringで定義されている同等の暗黙的な演算子の代わりに使用されることに注意してください。
つまり、Codegen は LangVersions 間で異なります (暗黙的な演算子は C# 13 で使用され、静的メソッド AsSpan は C# 14 で使用されます)。
一方、変換は .NET Framework で生成できます ( AsSpan メソッドは存在しますが、 string 演算子には存在しません)。
明示的な配列から (ReadOnly)Span への変換では、最初にソース配列から宛先要素型の配列に明示的に変換され、次に暗黙的な変換と同じヘルパーを使用して (ReadOnly)Span に変換されます。つまり、対応する op_Implicit(T[])が使用されます。
式からの変換の向上
式からの変換 (§12.6.4.5) が更新され、暗黙的にスパン変換を優先するようになりました。 これは、コレクション表現のオーバーロード解決の変更に基づいています。
C₁式から型Eに変換する暗黙的な変換T₁と、C₂式から型Eに変換する暗黙的な変換T₂を考えると、 次のいずれかが保持されている場合、C₁は よりもC₂になります。
Eはコレクション式であり、C₁は式からのより優れたコレクション変換です、C₂よりも優れています。Eは コレクション式 ではなく、次のいずれかが保持されます。
EがT₁と完全に一致し、EがT₂と完全に一致しませんET₁とT₂のどちらとも完全に一致せず、C₁は暗黙的なスパン変換であり、C₂は暗黙的なスパン変換ではありませんEはT₁とT₂の両方と一致するかどちらとも一致せず、C₁とC₂は両方とも暗黙的なスパン変換であるかどちらも暗黙的なスパン変換ではなく、T₁は よりもT₂であるEはメソッド グループであり、T₁は変換C₁のメソッド グループの 1 つの最適なメソッドと互換性があり、T₂は変換用のメソッド グループの 1 つの最適なメソッドと互換性がありませんC₂
コンバージョン ターゲットの向上
より適切な変換ターゲット (§12.6.4.7) が更新され、ReadOnlySpan<T>よりもSpan<T>が優先されます。
2 種類の
と がある場合、次のいずれかを満たす場合には、 は よりも の 優れた変換ターゲットです。
T₁がSystem.ReadOnlySpan<E₁>、T₂がSystem.Span<E₂>され、E₁からE₂への ID 変換が存在するT₁がSystem.ReadOnlySpan<E₁>、T₂がSystem.ReadOnlySpan<E₂>され、T₁からT₂への暗黙的な変換が存在し、T₂からT₁への暗黙的な変換は存在しません- 少なくとも 1 つの
T₁またはT₂がSystem.ReadOnlySpan<Eᵢ>されておらず、System.Span<Eᵢ>ではなくT₁からT₂への暗黙的な変換が存在し、T₂からT₁への暗黙的な変換は存在しません- ...
設計会議:
- https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-12-04.md#preferring-readonlyspant-over-spant-conversions
- https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-12-09.md#first-class-span-open-questions
改善のコメント
式からの より適切な変換 規則では、新しいスパン変換によってオーバーロードが適用可能になるたびに、新しく適用されるオーバーロードが優先されるため、別のオーバーロードとの潜在的なあいまいさが回避されるようにする必要があります。
この規則がない場合、C# 13 で正常にコンパイルされた次のコードでは、拡張メソッドレシーバーに適用できる配列から ReadOnlySpan への新しい標準の暗黙的な変換が原因で、C# 14 であいまいエラーが発生します。
using System;
using System.Collections.Generic;
var a = new int[] { 1, 2, 3 };
a.M();
static class E
{
public static void M(this IEnumerable<int> x) { }
public static void M(this ReadOnlySpan<int> x) { }
}
この規則では、以前はあいまいさが生じる新しい API を導入することもできます。次に例を示します。
using System;
using System.Collections.Generic;
C.M(new int[] { 1, 2, 3 }); // would be ambiguous before
static class C
{
public static void M(IEnumerable<int> x) { }
public static void M(ReadOnlySpan<int> x) { } // can be added now
}
Warnung
LangVersion >= 14にのみ存在するスパン変換に対してより良いルールが定義されているため、API 作成者は、LangVersion <= 13でサポート ユーザーを維持する場合、このような新しいオーバーロードを追加することはできません。
たとえば、.NET 9 BCL でこのようなオーバーロードが導入された場合、TFM net9.0 アップグレードしても LangVersion を低く維持しているユーザーは、既存のコードのあいまいさエラーを受け取ります。
以下のオープンな質問も参照してください。
型の推定
仕様の型推論セクションを次のように更新します ( bold の変更)。
12.6.3.9 正確な推論
型 から型
UへのVは、次のように行われます。
Vが 固定されていないXᵢの 1 つである場合、UはXᵢの正確な境界のセットに追加されます。- それ以外の場合、
V₁...VₑとU₁...Uₑのセットは、次のいずれかのケースが適用されるかどうかを確認することによって決定されます。
Vは配列型V₁[...]であり、Uは同じランクの配列型U₁[...]ですVはSpan<V₁>で、Uは配列型U₁[]またはSpan<U₁>VはReadOnlySpan<V₁>で、Uは配列型U₁[]またはSpan<U₁>またはReadOnlySpan<U₁>Vは型V₁?で、Uは型U₁ですVは構築された型C<V₁...Vₑ>で、Uは構築された型C<U₁...Uₑ>です
これらのケースのいずれかが該当する場合、各 について、対応するUᵢへのVᵢを行います。- それ以外の場合、推論は行われません。
12.6.3.10 下限推論
型
U型 のVは、次のように行われます。
Vが 固定されていないXᵢの 1 つである場合、UはXᵢの下限セットに追加されます。- それ以外の場合、
Vが型V₁?で、UがU₁?型である場合は、U₁からV₁への下限推論が行われます。- それ以外の場合、
U₁...UₑとV₁...Vₑのセットは、次のいずれかのケースが適用されるかどうかを確認することによって決定されます。
Vは配列型V₁[...]で、Uは同じランクの配列型U₁[...]ですVはSpan<V₁>で、Uは配列型U₁[]またはSpan<U₁>VはReadOnlySpan<V₁>で、Uは配列型U₁[]またはSpan<U₁>またはReadOnlySpan<U₁>Vは、IEnumerable<V₁>、ICollection<V₁>、IReadOnlyList<V₁>>、IReadOnlyCollection<V₁>またはIList<V₁>のいずれかであり、Uは 1 次元配列型U₁[]ですVは、構築されたclass、struct、interface、またはdelegate型C<V₁...Vₑ>であり、ユニークな型C<U₁...Uₑ>が存在します。そして、U(または、Uが型parameterの場合、その有効な基底クラスまたはその有効なインターフェースセットのメンバー)が、inheritsのいずれかと(直接または間接的に)同一であるか、またはC<U₁...Uₑ>を(直接または間接的に)実装します。- (「一意性」の制約は、インターフェース
C<T>{} class U: C<X>, C<Y>{}の場合、UはC<T>またはU₁である可能性があるため、XからYへの推論では推論が行われないことを意味します。)
これらのケースのいずれかが適用される場合、次のように、各Uᵢから対応するVᵢへの推論が行われます。Uᵢが参照型であることが不明な場合は、 正確な推論 が行われます- それ以外の場合、
Uが配列型の場合、「下限推論」が行われV
VがSpan<Vᵢ>の場合は、正確な推論が行われますVが配列型またはReadOnlySpan<Vᵢ>の場合は、lower バインド推論が行われます- それ以外の場合、
UがSpan<Uᵢ>の場合、推論はVの種類によって異なります。
VがSpan<Vᵢ>の場合は、正確な推論が行われますVがReadOnlySpan<Vᵢ>の場合は、より低い推論が行われます- それ以外の場合、
UがReadOnlySpan<Uᵢ>で、VがReadOnlySpan<Vᵢ>である場合は、 下限 推論が行われます。- それ以外の場合、
VがC<V₁...Vₑ>である場合、推論はi-thのC型パラメーターに依存します。
- 共変の場合、 下限推論 が行われます。
- 反変の場合は、 上限推論 が行われます。
- 不変の場合は、 正確な推論 が行われます。
- それ以外の場合、推論は行われません。
上限推論に関する規則はありません、それに到達することが不可能であるためです。
型推論は上限として開始されることはなく、下限推論と反変型パラメーターを経由する必要があります。
" Uᵢ が参照型であることが不明な場合は 、正確な推論 が行われます" というルールのため、ソース型引数を Span/ReadOnlySpan できませんでした (参照型にすることはできません)。
ただし、上限スパン推論は、ソースの種類が Span/ReadOnlySpanの場合にのみ適用されます。これは、次のような規則があるためです。
UはSpan<U₁>で、Vは配列型V₁[]またはSpan<V₁>UはReadOnlySpan<U₁>で、Vは配列型V₁[]またはSpan<V₁>またはReadOnlySpan<V₁>
重大な変更
既存のシナリオの変換を変更する提案として、この提案ではいくつかの新しい破壊的変更が導入されます。 以下にいくつか例を示します。
配列での Reverse の呼び出し
x.Reverse()を呼び出す際、xがT[]型のインスタンスである場合、以前はIEnumerable<T> Enumerable.Reverse<T>(this IEnumerable<T>)にバインドされていましたが、現在はvoid MemoryExtensions.Reverse<T>(this Span<T>)にバインドされます。
残念ながら、これらの API には互換性がありません (後者はインプレースで反転を行い、 voidを返します)。
.NET 10 では、配列固有のオーバーロード IEnumerable<T> Reverse<T>(this T[])を追加することでこれを軽減します。 https://github.com/dotnet/runtime/issues/107723を参照してください。
void M(int[] a)
{
foreach (var x in a.Reverse()) { } // fine previously, an error now (`Reverse` returns `void`)
foreach (var x in Enumerable.Reverse(a)) { } // workaround
}
関連項目も参照:
- https://developercommunity.visualstudio.com/t/Extension-method-SystemLinqEnumerable/10790323
- https://developercommunity.visualstudio.com/t/Compilation-Error-When-Calling-Reverse/10818048
- https://developercommunity.visualstudio.com/t/Version-17131-has-an-obvious-defect-th/10858254
- https://developercommunity.visualstudio.com/t/Visual-Studio-2022-update-breaks-build-w/10856758
- https://github.com/dotnet/runtime/issues/111532
- https://developercommunity.visualstudio.com/t/Backward-compatibility-issue-:-IEnumerab/10896189#T-ND10896782
設計会議: https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-09-11.md#reverse
あいまい さ
次の例では、以前は Span オーバーロードの型推論が失敗しましたが、配列から Span への型推論は成功するため、あいまいです。
これを回避するには、ユーザーは .AsSpan() を使用するか、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 x = new int[] { 1, 2 };
var s = new ArraySegment<int>(x, 1, 1);
Assert.Equal(x, s); // previously Assert.Equal<T>(T, T), now ambiguous with Assert.Equal<T>(Span<T>, Span<T>)
Assert.Equal(x.AsSpan(), s); // workaround
xUnit では、これを軽減するためにオーバーロードが追加されています: https://github.com/xunit/xunit/discussions/3021。
設計会議: https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-09-11.md#new-ambiguities
共変配列
IEnumerable<T> を取るオーバーロードは共変配列で機能しますが、Span<T> (現在は好まれています) を取るオーバーロードは、スパン変換が共変配列に対して ArrayTypeMismatchException をスローするため、機能しません。
おそらく、 Span<T> オーバーロードは存在せず、代わりに ReadOnlySpan<T> を取る必要があります。
これを回避するには、ユーザーは.AsEnumerable()を使用することができ、またはAPI作成者はOverloadResolutionPriorityAttributeを使用するか、ReadOnlySpan<T>オーバーロードを追加することができます。ReadOnlySpan<T>の使用は優れた方法であるという原則から推奨されます。
string[] s = new[] { "a" };
object[] o = s;
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);
}
設計会議: https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-09-11.md#covariant-arrays
スパンよりも ReadOnlySpan を優先する
ベターネス ルールにより、共変配列シナリオでのArrayTypeMismatchExceptionを回避するために、Span オーバーロードよりも ReadOnlySpan オーバーロードが優先されます。
オーバーロードが戻り値の型によって異なる場合など、一部のシナリオではコンパイルが中断される可能性があります。
double[] x = new double[0];
Span<ulong> y = MemoryMarshal.Cast<double, ulong>(x); // previously worked, now a compilation error (returns ReadOnlySpan, not Span)
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;
}
https://github.com/dotnet/roslyn/issues/76443を参照してください。
式ツリー
式ツリー内であっても、 MemoryExtensions.Contains のようなスパンを取るオーバーロードは、 Enumerable.Containsなどの従来のオーバーロードよりも優先されますが、ref 構造体はインタープリター エンジンではサポートされていません。
Expression<Func<int[], int, bool>> exp = (array, num) => array.Contains(num);
exp.Compile(preferInterpretation: true); // fails at runtime in C# 14
Expression<Func<int[], int, bool>> exp2 = (array, num) => Enumerable.Contains(array, num); // workaround
exp2.Compile(preferInterpretation: true); // ok
同様に、LINQ-to-SQL などの翻訳エンジンでは、ツリー訪問者が代わりにEnumerable.Containsに遭遇するため、MemoryExtensions.Containsを期待する場合は、これに対応する必要があります。
関連項目も参照:
- https://github.com/dotnet/runtime/issues/109757
- https://github.com/dotnet/docs/issues/43952
- https://github.com/dotnet/efcore/issues/35100
- https://github.com/dotnet/csharplang/discussions/8959
設計会議:
- https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-12-04.md#conversions-in-expression-trees
- https://github.com/dotnet/csharplang/blob/main/meetings/2025/LDM-2025-01-06.md#ignoring-ref-structs-in-expressions
継承によるユーザー定義変換
単純スパン変換標準の暗黙的な変換の一覧に追加することで、ユーザー定義の変換が型階層に関係する場合の動作を変更する可能性があります。 この例は、新しい C# 14 の動作と同じように既に動作する整数シナリオと比較して、その変更を示しています。
Span<string> span = [];
var d = new Derived();
d.M(span); // Base today, Derived tomorrow
int i = 1;
d.M(i); // Derived today, demonstrates new behavior
class Base
{
public void M(Span<string> s)
{
Console.WriteLine("Base");
}
public void M(int i)
{
Console.WriteLine("Base");
}
}
class Derived : Base
{
public static implicit operator Derived(ReadOnlySpan<string> r) => new Derived();
public static implicit operator Derived(long l) => new Derived();
public void M(Derived s)
{
Console.WriteLine("Derived");
}
}
関連項目: https://github.com/dotnet/roslyn/issues/78314
拡張メソッドの検索
拡張メソッドの参照で スパン変換 を単純にできるようにすることで、オーバーロードの解決によって解決される拡張メソッドを変更できる可能性があります。
namespace N1
{
using N2;
public class C
{
public static void M()
{
Span<string> span = new string[0];
span.Test(); // Prints N2 today, N1 tomorrow
}
}
public static class N1Ext
{
public static void Test(this ReadOnlySpan<string> span)
{
Console.WriteLine("N1");
}
}
}
namespace N2
{
public static class N2Ext
{
public static void Test(this Span<string> span)
{
Console.WriteLine("N2");
}
}
}
未解決の質問
無制限の改善ルール
より良いルール LangVersion で無条件にする必要がありますか? これにより、API 作成者は、古い LangVersions や他のコンパイラや言語 (VB など) でユーザーを壊すことなく、IEnumerable と同等の機能が存在する新しい Span API を追加できます。 ただし、これは、ツールセットを更新した後 (LangVersion または TargetFramework を変更せずに) ユーザーが異なる動作を得ることができることを意味します。
- コンパイラは異なるオーバーロードを選択できます (技術的には破壊的変更ですが、これらのオーバーロードが同等の動作をすることを願っています)。
- 他の休憩が発生する可能性があり、現時点では不明です。
OverloadResolutionPriorityAttributeは古い LangVersion でも無視されるため、これを完全に解決できないことに注意してください。
ただし、属性を認識する必要がある VB からのあいまいさを回避するために使用できる必要があります。
より多くのユーザー定義変換を無視する
言語定義の暗黙的および明示的なスパン変換がある型ペアのセットを定義しました。
言語定義のスパン変換が T1 から T2 に存在する場合、T1 から T2 へのユーザー定義の変換は無視されます(スパンとユーザー定義の変換が暗黙的または明示的であるかどうかにかかわらず)。
これにはすべての条件が含まれているため、たとえば、 Span<object> から ReadOnlySpan<string> へのスパン変換はありません ( Span<T> から ReadOnlySpan<U> へのスパン変換がありますが、その T : Uを保持する必要があります)。そのため、存在する場合、ユーザー定義の変換はそれらの型間で考慮されます (変換演算子はジェネリック パラメーターを持つことができないため、 Span<T> から ReadOnlySpan<string> への特殊な変換である必要があります)。
対応する言語定義スパン変換が存在しない配列/Span/ReadOnlySpan/string 型の他の組み合わせ間のユーザー定義変換も無視する必要がありますか?
たとえば、 ReadOnlySpan<T> から Span<T>へのユーザー定義の変換がある場合は、無視する必要がありますか?
考慮する可能性を指定します。
-
T1からT2へのスパン変換が存在する場合は常に、T1からT2へのT2からT1or へのユーザー定義の変換は無視します。 -
間で変換する場合、ユーザー定義の変換は考慮されません
- 任意の 1 次元
array_typeとSystem.Span<T>/System.ReadOnlySpan<T>、 -
System.Span<T>/System.ReadOnlySpan<T>の任意の組み合わせ、 -
stringとSystem.ReadOnlySpan<char>.
- 任意の 1 次元
- 上記のように、最後の箇条書きを次のように置き換えます。
-
stringおよびSystem.Span<char>/System.ReadOnlySpan<char>。
-
- 上記のように、最後の箇条書きを次のように置き換えます。
-
stringおよびSystem.Span<T>/System.ReadOnlySpan<T>。
-
技術的には、この仕様では、これらのユーザー定義変換の一部を定義することも禁止しています。ユーザー定義以外の変換が存在する型間でユーザー定義演算子を定義することはできません (§10.5.2)。
しかし、Roslynは意図的に仕様のこの部分に違反しています。また、 Span と string の間のような一部の変換は、とにかく許可されています (これらの型間に言語で定義された変換は存在しません)。
ただし、変換を単に無視するのではなく、これらの変換を定義することを禁止し、少なくともこれらの新しいスパン変換についてスペック違反から抜け出すことも考えられます。つまり、これらの変換が定義されている場合には(おそらくBCLによって既に定義されているものを除く)、Roslynを変更してコンパイル時エラーを実際に報告するようにします。
選択肢
そのままにしておきます。
C# feature specifications