当具有相关参数的通用方法渗透到某些情况下会产生意外结果的类型时。如果我明确指定类型,一切都可以正常工作而没有任何进一步的更改。
IEnumerable<List<string>> someStringGroups = null; // just for demonstration
IEqualityComparer<IEnumerable<string>> someSequenceComparer = null;
var grouped = someStringGroups
.GroupBy(x => x, someSequenceComparer);
当然,上述代码不打算执行,但它表明grouped
的结果类型是IEnumerable<IEnumerable<string>,List<string>>
而不是IEnumerable<List<string>,List<string>>
,因为x => x
。
如果我明确指定所有内容都可以。
var grouped = someStringGroups
.GroupBy<List<string>,List<string>>(x => x, someSequenceComparer);
如果我不使用明确的比较,一切也可以按照指示。
我认为问题在于,在IEqualityComparer<>
接口的协方差上,采用提供的参数类型的最不常见的分母(IEnumerable<string>
(采取优先级。我本来会期望相反,即一种通用方法应推断出参数所满足的最特定类型。
问题是:这是错误还是已记录的行为? 我本来会期望相反,即一种通用方法应推断出参数所满足的最特定类型。 基于什么? 您看到的行为已记录并符合C#规范。您可能想象的,类型推理规范相当复杂。我不会在这里引用整个内容,但是如果您有兴趣,您可以自己查看它。相关部分是 7.5.2类型推荐。 根据您写的评论,我认为至少一部分混乱源于您忘记了此方法有三个参数,而不是两个参数(这影响了推论的过程(。此外,似乎您期望第二个参数 ,但我认为主要的是您期望类型推理对类型差异的攻击性比实际上所需的规范更具侵略性。 在类型推理期间,在规范中描述了第一阶段keySelector
委托会影响类型推理,而在这种情况下它不在(至少不是直接&Hellip;它都会在类型参数之间产生依赖性,但不会以物质的方式产生依赖关系。(。
您称此 GroupBy()
的超载:
public static IEnumerable<IGrouping<TKey, TSource>> GroupBy<TSource, TKey>(
this IEnumerable<TSource> source,
Func<TSource, TKey> keySelector,
IEqualityComparer<TKey> comparer)
有两个类型的参数需要推断,TSource
和TKey
。在推断期间,编译器确实确定类型参数的上限和下限。但是这些基于传递给方法调用的类型。编译器不搜索满足类型要求的替代基础或派生类型。
so,对于TSource
,确定了List<string>
的下限,而对于TKey
,确定了IEnumerable<string>
的上限( 7.5.2.9较低的推论(。这些类型是您提供给呼叫的内容,因此编译器使用的是。
在第二阶段,尝试修复类型。TSource
不取决于任何其他参数,因此首先将其固定为List<string>
。第二阶段的第二次绕组修复了TKey
。虽然类型差异允许 TKey
设置的界限可容纳List<string>
,但没有必要,因为根据其范围,您所传递的类型可以直接使用。
因此,您可以使用IEnumerable<string>
结束。
当然,编译器将List<string>
用作TKey
的编译器将是合法的(如果不符合规范(。如果参数明确施放,我们可以看到这项工作:
var grouped2 = someStringGroups
.GroupBy(x => x, (IEqualityComparer<List<string>>)someSequenceComparer);
这更改了用于调用的表达式的类型,因此使用了所使用的边界,最后是推理过程中选择的实际类型。但是在原始呼叫中,编译器在推断期间不需要使用与您指定的类型不同的类型,即使允许使用,因此不需要。
C#规格上有一些相当毛茸茸的零件。类型推断绝对是其中之一,坦率地说,我不是解释规范这一部分的专家。这会使我的头部受伤,而且肯定有一些我可能不理解的角落案例(即我怀疑我是否可以实现这一规范,而没有更多的研究(。但是我相信以上是对与您的问题相关的部分的正确解释,我希望我在解释它方面做了合理的工作。
我很确定这是预期的行为。
我们感兴趣的方法签名是:
public static IEnumerable<IGrouping<TKey, TSource>> GroupBy<TSource, TKey>(
this IEnumerable<TSource> source,
Func<TSource, TKey> keySelector,
IEqualityComparer<TKey> comparer
)
source
是IEnumerable<List<string>>
类型,因此TSource
的自然选择是List<string>
。comparer
是类型IEqualityComparer<IEnumerable<string>>
,因此TKey
的自然选择是IEnumerable<string>
。
如果我们然后查看最后一个参数keySelector
是x=>x
。这是否满足到目前为止的类型约束?是的,因为X是List<string>
,并且可以隐式转换为IEnumerable<string>
。
在这一点上,为什么编译器还要寻找更多?没有铸件的自然和明显的选择需要作品,因此它可以使用。如果您不喜欢它,您始终可以按照自己的方式进行选择,并明确说明通用参数。
或当然可以对IEqualityComparer<List<string>>
类型进行比较,在这种情况下,您的输出对象是您期望的类型(我希望您能看到为什么(。