C# 泛型类型推断与协变 - 错误或限制

标签 c# generics type-inference covariance

当具有依赖参数的泛型方法推断类型时,它在某些情况下会产生意想不到的结果。如果我明确指定类型,则一切正常,无需任何进一步更改。

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);

如果我不使用显式比较器,一切也会按预期工作。

我认为问题在于采用所提供参数类型的最小公分母 ( IEnumerable<string> ) 优先 优于 IEqualityComparer<> 的协方差界面。我的预期恰恰相反,即泛型方法应该推断出参数所满足的最具体的类型。

问题是:这是一个错误还是记录在案的行为?

最佳答案

I would have expected the opposite, i.e. a generic method should infer the most specific type that is satisfied by the arguments.

究竟是基于什么?

您看到的行为已记录并符合 C# 规范。正如您想象的那样,类型推断规范相当复杂。这里就不全文引用了,有兴趣的可以自行查阅。相关部分是7.5.2 类型推断

根据您所写的评论,我认为至少部分困惑源于您忘记了此方法有 三个 参数,而不是两个(这会影响推理的进行方式)。此外,您似乎期待第二个参数 keySelector委托(delegate),影响类型推断,当它在这种情况下不影响时(至少,不直接......它在类型参数之间创建依赖关系,但不是以实质方式)。

但我认为最主要的是您期望类型推断比规范实际要求的类型变化更积极。

在类型推断期间,规范中描述了发生的第一件事,在 7.5.2.1 第一阶段 下。出于本阶段的所有意图和目的,第二个参数被忽略。它没有为其参数显式声明类型(尽管如此,如果有也没有关系)。在此阶段,类型推断开始为类型参数开发界限,但并不固定参数本身。

您正在调用 GroupBy() 的重载:

public static IEnumerable<IGrouping<TKey, TSource>> GroupBy<TSource, TKey>(
    this IEnumerable<TSource> source,
    Func<TSource, TKey> keySelector,
    IEqualityComparer<TKey> comparer)

有两个类型参数需要推断,TSourceTKey .在推理期间,编译器确实确定了类型参数的上限和下限。但这些都是基于传递给方法调用的类型。编译器不会搜索满足类型要求的替代基类型或派生类型。

因此,对于 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# 规范有一些相当复杂的部分。类型推断绝对是其中之一,坦率地说,我不是解释规范这一部分的专家。这让我头疼,而且肯定有一些我可能不理解的更具挑战性的极端情况(即,我怀疑我是否可以实现规范的这一部分,而不需要更多的研究)。但我相信以上是对与您的问题相关的部分的正确解释,我希望我已经做了合理的解释。

关于C# 泛型类型推断与协变 - 错误或限制,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/44777233/

相关文章:

c# - 为什么枚举 DirectoryEntry 子项在 WinNT 域上只返回 20 个结果? C#

C# 泛型类运算符不工作

Java 接口(interface)反射

java - 奇怪的 ClassCastException

c++ - decltype 和括号

c# - Entity Framework 6 Fluent 映射 - 一对多代码优先

c# - 无法从程序集 'OpenQA.Selenium.Internal.IWrapsElement' 加载类型 'WebDriver, Version=4.0.0.0, Culture=neutral, PublicKeyToken=null'

c# - 你调用的对象是空的。在 MVC 布局中 = null

c# - 为什么使用命名空间以一种不直观的方式工作?

c# - 为什么不能推断出这些泛型类型?