共用方式為


從集合表達式元素進行更好的轉化

注意

本文是功能規格。 規格可作為功能的設計檔。 其中包含建議的規格變更,以及功能設計和開發期間所需的資訊。 這些文章會發佈,直到提議的規格變更完成並併併入目前的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中的規則。

這些規則包括:

假設有隱含轉換 C₁ 將表達式 E 轉換為類型 T₁,還有一個隱含轉換 C₂ 將表達式 E 轉換為類型 T₂,如果下列其中一項成立,則 C₁更好的轉換C₂

  • 集合表達式 是表達式 比 更好的集合轉換
  • E 不是 集合表達式,且符合以下其中一條件:
    • E 完全符合 T₁,而 ET₂ 不完全相符
    • E 精確地符合 T₁T₂任一者或兩者皆不,而 T₁ 是比 更好的
  • E 是方法群組...

我們新增一個新定義,以促進從表達式到集合 的更佳轉換,如下所示。

鑒於:

  • E 是由元素表達式 [EL₁, EL₂, ..., ELₙ] 組成的集合表達式
  • T₁T₂ 是集合類型
  • E₁T₁ 的元素類型
  • E₂T₂ 的元素類型
  • CE₁ᵢ 是將 ELᵢ 轉換為 E₁ 的系列
  • CE₂ᵢ 是將 ELᵢ 轉換為 E₂ 的系列

如果有從 E₁ 轉換成 E₂的身分識別轉換,則元素轉換會和彼此一樣好。 否則,轉換成 的元素會比轉換 到 的元素在 方面更好:

  • 對於每個 ELᵢCE₁ᵢ 至少和 CE₂ᵢ一樣好。
  • 至少存在一個條件 i,使得 CE₁ᵢCE₂ᵢ 更好。否則,兩組元素轉換彼此無優劣,且其效果也不彼此相同。
    如果 ELᵢ 不是展開元素,則使用較佳的轉換來比較表達式。 如果 ELᵢ 是散佈元素,我們會使用更佳的轉換方式,將散佈集合的元素類型分別轉換成 E₁E₂

C₁ 是比 更好的集合轉換,若

  • T₁T₂ 不是 範圍類型,而且 T₁ 可以隱含轉換成 T₂,但 T₂ 無法隱含轉換成 T₁
  • 沒有 的身分識別轉換,而 的項目轉換比 轉換成 的專案 更好, 或
  • E₁ 的身分識別轉換成 E₂,且下列其中一項保留:
    • T₁System.ReadOnlySpan<E₁>,且 T₂System.Span<E₂>
    • T₁System.ReadOnlySpan<E₁>System.Span<E₁>,而 T₂ 是一個 array_or_array_interface,具有 元素類型E₂

否則,兩個集合類型都不更好,而且結果模棱兩可。

注意

這些規則表示公開多載的方法會採用不同的項目類型,而且集合類型之間沒有轉換,對於空集合表達式來說模棱兩可。 例如:

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 更糟?