我本来期望相反的情况,即通用方法应该推断出参数满足的最具体的类型。
到底基于什么?
您看到的行为已记录并符合 C# 规范。正如您可能想象的那样,类型推断规范相当复杂。这里我就不完整引用了,有兴趣的话可以自己回顾一下。相关部分是7.5.2 类型推断.
根据您写的评论,我认为至少部分混乱源于您忘记了three此方法的参数,而不是两个(这会影响推理的进行方式)。此外,您似乎期待第二个参数,即keySelector
委托,以影响类型推断,但在这种情况下不会(至少,不是直接......它在类型参数之间创建依赖关系,但不是以实质性方式)。
但我认为最主要的是,您期望类型推断对类型差异的处理比规范实际要求的更加积极。
在类型推断期间,发生的第一件事在规范中进行了描述,位于7.5.2.1 第一阶段。出于此阶段的所有意图和目的,第二个参数被忽略。它没有显式声明其参数的类型(不过,即使有也没关系)。在此阶段,类型推断开始发展bounds用于类型参数,但不固定参数本身。
您将这种超载称为GroupBy()
:
public static IEnumerable<IGrouping<TKey, TSource>> GroupBy<TSource, TKey>(
this IEnumerable<TSource> source,
Func<TSource, TKey> keySelector,
IEqualityComparer<TKey> comparer)
有两个类型参数需要推断,TSource
and TKey
。在推理过程中,编译器确实会确定类型参数的上限和下限。但这些是基于传递给方法调用的类型的。编译器不会搜索满足类型要求的替代基类型或派生类型。
So, for TSource
,下界为List<string>
被识别,而对于TKey
,上限为IEnumerable<string>
被识别(7.5.2.9 下界推论)。这些类型是您提供给调用的类型,因此这就是编译器使用的类型。
在第二阶段,尝试修复类型。TSource
不依赖于任何其他参数,因此首先将其固定,如下所示List<string>
。第二阶段的第二次复飞修复TKey
。而类型方差allows设定的界限为TKey
以适应List<string>
,没有必要,因为根据它的边界,你传入的类型可以直接使用。
因此,你最终会得到IEnumerable<string>
反而。
当然,编译器使用它是合法的(如果不符合规范)List<string>
as TKey
反而。如果相应地显式转换参数,我们可以看到这项工作:
var grouped2 = someStringGroups
.GroupBy(x => x, (IEqualityComparer<List<string>>)someSequenceComparer);
这会更改用于调用的表达式的类型,从而更改所使用的边界,当然最后还会更改推理过程中选择的实际类型。但在最初的调用中,编译器在推理过程中不需要使用与您指定的类型不同的类型,即使它是被允许的,所以它没有。
C# 规范有一些相当棘手的部分。类型推断绝对是其中之一,坦率地说,我不是解释规范这一部分的专家。这让我头疼,而且肯定有一些更具挑战性的极端情况我可能不理解(即我怀疑我可以实施规范的这一部分,无需进行更多研究)。但我相信以上是对与你的问题相关的部分的正确解释,我希望我已经做了合理的解释。