注意
本文是特性规范。 该规范充当该功能的设计文档。 它包括建议的规范更改,以及功能设计和开发过程中所需的信息。 这些文章将发布,直到建议的规范更改最终确定并合并到当前的 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 更糟糕呢?