注意
本文是特性规范。 该规范充当该功能的设计文档。 它包括建议的规范更改,以及功能设计和开发过程中所需的信息。 这些文章将发布,直到建议的规范更改最终确定并合并到当前的 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
是 集合表达式,C₁
是比C₂
表达式更优的集合转换E
不是 集合表达式,并且以下任一条件成立:
E
完全匹配T₁
,E
与T₂
不完全匹配E
要么与T₁
和T₂
完全匹配,要么都不匹配,而T₁
是比T₂
更好的 转换目标E
是方法组...
我们为 添加一个新定义,以便更好地将表达式转换成集合,如下所示:
假定为:
E
是一个包含元素表达式[EL₁, EL₂, ..., ELₙ]
的集合表达式T₁
和T₂
是集合类型E₁
是T₁
的元素类型E₂
是T₂
的元素类型-
CE₁ᵢ
是从ELᵢ
到E₁
的一系列转换 -
CE₂ᵢ
是从ELᵢ
到E₂
的一系列转换
如果存在从 E₁
到 E₂
的标识转换,则元素之间的转换效果同样出色。 否则,到 E₁
的元素转换 优于元素转换 到 E₂
(如果:
- 对于每个
ELᵢ
,CE₁ᵢ
至少和CE₂ᵢ
一样好,而且 - 至少有一个 i,其中
CE₁ᵢ
比CE₂ᵢ
更好。否则,两组元素转换都不比另一组更好,它们也不如彼此一样好。
如果ELᵢ
不是一个 spread 元素,则使用更好的表达式转换进行转换比较。 如果ELᵢ
是展开元素,我们可以分别更加有效地将展开集合的元素类型转换为E₁
或E₂
。
C₁
是一种表达式更好的集合转换(相较于 C₂
),但前提是:
T₁
和T₂
都不是 跨度类型,T₁
可隐式转换为T₂
,而T₂
不能隐式转换为T₁
,或者...-
E₁
没有到E₂
的标识转换,并且到E₁
的元素转换优于到E₂
的元素转换,或者 -
E₁
对E₂
进行标识转换,且以下条件之一成立: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
更糟糕呢?