次の方法で共有


コレクション式要素からの変換の改善

メモ

この記事は機能仕様についてです。 仕様は、機能の設計ドキュメントとして使用できます。 これには、提案された仕様の変更および機能の設計と開発時に必要な情報が含まれます。 これらの記事は、提案された仕様の変更が決定され、現在の ECMA 仕様に組み込まれるまで公開されます。

機能の仕様と行われた実装では、いくつかの違いがあることがあります。 これらの違いは、関連する言語設計ミーティング (LDM) メモに取り上げられています。

機能仕様を C# 言語標準に導入するプロセスの詳細については、仕様に関する記事を参照してください。

チャンピオンの課題: https://github.com/dotnet/csharplang/issues/8374

まとめ

params との一貫性を高め、現在のあいまいさのシナリオをより適切に処理できるようにすることを目的とした、より優れた変換規則に対する更新。 たとえば、ReadOnlySpan<string>ReadOnlySpan<object> は現在、[""] のオーバーロード解決中にあいまいさを引き起こす可能性があります。

詳細な設計

式ルールからのより適切な変換を次に示します。 これらは https://github.com/dotnet/csharplang/blob/main/proposals/csharp-12.0/collection-expressions.md#overload-resolution のルールを置き換えます。

次のような規則です。

E式から型T₁ に変換する暗黙的な変換 C₁ と、 E 式から型 T₂に変換する暗黙的な変換 C₂ を考えると、 次のいずれかが保持されている場合、 C₁C₂ よりも 優れた変換 になります。

  • Eコレクション式であり、C₁ より優れたC₂
  • Eコレクション式ではなく、次のいずれかを保持します。
    • ET₁ と完全に一致し、ET₂ と完全に一致しません
    • ET₁ および T₂の両方またはどちらにも完全に一致し、T₁ よりも T₂ です
  • E はメソッド グループです...

次のように、式からのコレクション変換を改善するための新しい定義を追加します。

たとえば、以下のように指定したとします。

  • E は要素式が [EL₁, EL₂, ..., ELₙ] のコレクション式です
  • T₁T₂ はコレクション型です
  • E₁T₁ の要素型です
  • E₂T₂ の要素型です
  • CE₁ᵢELᵢ から E₁ への一連の変換です
  • CE₂ᵢELᵢ から E₂ への一連の変換です

E₁ から E₂ への ID 変換がある場合、要素の変換は互いに同じになります。 それ以外の場合、E₁ への要素変換は、次の場合に E₂ への要素変換よりも優れています

  • すべての ELᵢ について、CE₁ᵢ は少なくとも CE₂ᵢ と同じくらい優れています。
  • CE₁ᵢCE₂ᵢ よりも優れている i が少なくとも 1 つ存在します。それ以外の場合、どちらの要素変換セットも他の要素の変換よりも優れておらず、互いほど良くはありません。
    ELᵢ がスプレッド要素でない場合は、式からのより適切な変換を使用して変換比較が行われます。 ELᵢ がスプレッド要素の場合は、スプレッド コレクションの要素型から E₁ または E₂ へのにより優れた変換を使用します。

次に当てはまる場合、C₁ より優れたC₂:

  • はともに型の ではなく、 は暗黙的に に変換可能であり、 は暗黙的に に変換できません。
  • E₁ には E₂ への ID 変換がありません。また、E₁ への要素変換は E₂ への要素変換よりも優れています
  • E₁ には E₂ への ID 変換があり、次のいずれかが保持されます。
    • T₁System.ReadOnlySpan<E₁>T₂System.Span<E₂>、または
    • T₁System.ReadOnlySpan<E₁> または System.Span<E₁> であり、T₂要素型E₂を持つ array_or_array_interface

それ以外の場合は、どちらのコレクション型も優れていないため、結果はあいまいです。

メモ

これらのルールは、異なる要素型を受け取り、コレクション型間の変換を行わないオーバーロードを公開するメソッドは、空のコレクション式ではあいまいであることを意味します。 例

public void M(ReadOnlySpan<int> ros) { ... }
public void M(Span<int?> span) { ... }

M([]); // Ambiguous

シナリオ:

単純な英語では、コレクション型自体は同じであるか、明確に優れている必要があります (つまり、List<T>List<T> は同じであり、List<T>IEnumerable<T> よりも明確に優れています。また、List<T>HashSet<T> を比較することはできません)。また、より良いコレクション型の要素変換も同じか、優れている必要があります (つまり、[""]ReadOnlySpan<object>Span<string> を決定することはできません。 ユーザーがその決定を行う必要があります)。 この例を次にいくつか示します。

T₁ T₂ E C₁ 変換 C₂ 変換 CE₁ᵢCE₂ᵢ の比較 結果
List<int> List<byte> [1, 2, 3] [Identity, Identity, Identity] [Implicit Constant, Implicit Constant, Implicit Constant] CE₁ᵢ の方が良い List<int> が選択される
List<int> List<byte> [(int)1, (byte)2] [Identity, Implicit Numeric] 該当なし T₂ は適用できない List<int> が選択される
List<int> List<byte> [1, (byte)2] [Identity, Implicit Numeric] [Implicit Constant, Identity] どちらもより良いわけではない あいまい
List<int> List<byte> [(byte)1, (byte)2] [Implicit Numeric, Implicit Numeric] [Identity, Identity] CE₂ᵢ の方が良い List<byte> が選択される
List<int?> List<long> [1, 2, 3] [Implicit Nullable, Implicit Nullable, Implicit Nullable] [Implicit Numeric, Implicit Numeric, Implicit Numeric] どちらもより良いわけではない あいまい
List<int?> List<ulong> [1, 2, 3] [Implicit Nullable, Implicit Nullable, Implicit Nullable] [Implicit Numeric, Implicit Numeric, Implicit Numeric] CE₁ᵢ の方が良い List<int?> が選択される
List<short> List<long> [1, 2, 3] [Implicit Numeric, Implicit Numeric, Implicit Numeric] [Implicit Numeric, Implicit Numeric, Implicit Numeric] CE₁ᵢ の方が良い List<short> が選択される
IEnumerable<int> List<byte> [1, 2, 3] [Identity, Identity, Identity] [Implicit Constant, Implicit Constant, Implicit Constant] CE₁ᵢ の方が良い IEnumerable<int> が選択される
IEnumerable<int> List<byte> [(byte)1, (byte)2] [Implicit Numeric, Implicit Numeric] [Identity, Identity] CE₂ᵢ の方が良い List<byte> が選択される
int[] List<byte> [1, 2, 3] [Identity, Identity, Identity] [Implicit Constant, Implicit Constant, Implicit Constant] CE₁ᵢ の方が良い int[] が選択される
ReadOnlySpan<string> ReadOnlySpan<object> ["", "", ""] [Identity, Identity, Identity] [Implicit Reference, Implicit Reference, Implicit Reference] CE₁ᵢ の方が良い ReadOnlySpan<string> が選択される
ReadOnlySpan<string> ReadOnlySpan<object> ["", new object()] 該当なし [Implicit Reference, Identity] T₁ は適用できない ReadOnlySpan<object> が選択される
ReadOnlySpan<object> Span<string> ["", ""] [Implicit Reference] [Identity] CE₂ᵢ の方が良い Span<string> が選択される
ReadOnlySpan<object> Span<string> [new object()] [Identity] 該当なし T₁ は適用できない ReadOnlySpan<object> が選択される
ReadOnlySpan<InterpolatedStringHandler> ReadOnlySpan<string> [$"{1}"] [Interpolated String Handler] [Identity] CE₁ᵢ の方が良い ReadOnlySpan<InterpolatedStringHandler> が選択される
ReadOnlySpan<InterpolatedStringHandler> ReadOnlySpan<string> [$"{"blah"}"] [Interpolated String Handler] [Identity] - ただし定数 CE₂ᵢ の方が良い ReadOnlySpan<string> が選択される
ReadOnlySpan<string> ReadOnlySpan<FormattableString> [$"{1}"] [Identity] [Interpolated String] CE₂ᵢ の方が良い ReadOnlySpan<string> が選択される
ReadOnlySpan<string> ReadOnlySpan<FormattableString> [$"{1}", (FormattableString)null] 該当なし [Interpolated String, Identity] T₁ は適用されない ReadOnlySpan<FormattableString> が選択される
HashSet<short> Span<long> [1, 2] [Implicit Constant, Implicit Constant] [Implicit Numeric, Implicit Numeric] CE₁ᵢ の方が良い HashSet<short> が選択される
HashSet<long> Span<short> [1, 2] [Implicit Numeric, Implicit Numeric] [Implicit Constant, Implicit Constant] CE₂ᵢ の方が良い Span<short> が選択される

未解決の質問

他の型よりも ReadOnlySpan/Span をどの程度優先する必要がありますか?

現在指定されているように、次のオーバーロードはあいまいになります。

C.M1(["Hello world"]); // Ambiguous, no tiebreak between ROS and List
C.M2(["Hello world"]); // Ambiguous, no tiebreak between Span and List

C.M3(["Hello world"]); // Ambiguous, no tiebreak between ROS and MyList.

C.M4(["Hello", "Hello"]); // Ambiguous, no tiebreak between ROS and HashSet. Created collections have different contents

class C
{
    public static void M1(ReadOnlySpan<string> ros) {}
    public static void M1(List<string> list) {}

    public static void M2(Span<string> ros) {}
    public static void M2(List<string> list) {}

    public static void M3(ReadOnlySpan<string> ros) {}
    public static void M3(MyList<string> list) {}

    public static void M4(ReadOnlySpan<string> ros) {}
    public static void M4(HashSet<string> hashset) {}
}

class MyList<T> : List<T> {}

どの程度まで進みたいですか? List<T> バリアントは妥当と思われ、List<T> のサブタイプは適切に存在します。 しかし、HashSet バージョンはかなり異なるセマンティクスです、この API の ReadOnlySpan よりも実際に「悪い」ことをどのように確信していますか?