C# 泛型类型推断与协方差 - bug 或限制

2024-04-17

当具有依赖参数的泛型方法推断类型时,它在某些情况下会给出意外的结果。如果我明确指定类型,则一切都可以正常工作,无需任何进一步的更改。

IEnumerable<List<string>> someStringGroups = null; // just for demonstration
IEqualityComparer<IEnumerable<string>> someSequenceComparer = null;

var grouped = someStringGroups
  .GroupBy(x => x, someSequenceComparer);

当然,上面的代码并不打算被执行,但它演示了结果类型grouped is 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>) takes 优先级的协方差IEqualityComparer<>界面。我本来期望相反的情况,即通用方法应该推断出参数满足的最具体的类型。

问题是:这是一个错误或记录的行为?


我本来期望相反的情况,即通用方法应该推断出参数满足的最具体的类型。

到底基于什么?

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

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

C# 泛型类型推断与协方差 - bug 或限制 的相关文章

随机推荐

  • 如何可视化来自谷歌协议缓冲区的数据?

    我想使用谷歌协议缓冲区存储数据 另一种序列化格式也可以 然后有一个用户界面来浏览该数据 是否有 C 框架 API 可以让我做到这一点 例如 它可以使用protobuf的反射接口 然后将数据填充到Qt的QTableView 或从其他工具包 中
  • 从 C 中的 long 中提取单个数字

    我正在为我的 C 课程 第一门编程课程 做作业 作业的一部分是编写代码 让用户输入一个最多9位数的数字 程序需要判断这个数字是 递增 真递增 递减 真递减 增减 实减实增 不减不增 共7个选项 由于这是我们的第一个作业 我们不允许使用课堂上
  • 与 Jenkins 工作流程/管道并行运行阶段

    请注意 问题是基于旧的 现在称为 脚本化 管道格式 当使用 声明式管道 时 并行块可以嵌套在阶段块内 请参阅声明式管道 1 2 的并行阶段 https jenkins io blog 2017 09 25 declarative 1 我想知
  • Android 中卡片视图内带有三个点的小部件的名称是什么?

    带有三个点的小部件是什么 如何将其添加到我的应用程序中 这根本不是一个小部件 它是一个ImageButton 无边框风格 使用包含一个的溢出图标PopupMenu 如需文档教程访问http developer android com gui
  • ASP.NET-Core 2.0 在应用程序启动后添加/删除路由

    我需要添加 删除通过 IApplicaitonBuilder 在 Startup 类的 Configure 方法期间注册的自定义路由 启动后 我在 UseMvc 命令中调用 MapRoute 方法 将一堆自定义路由注册到我的控制器 这些路由
  • ListBox不显示绑定数据

    在我的 Xaml 中我有这个
  • 将 Bootstrap 导航栏中的元素居中

    无论我尝试什么 我都无法将 Bootstrap 导航栏中的某些内容居中 有什么解决方案吗 我尝试添加一个div 使用margin 0 auto or margin right auto margin left auto used cente
  • opencv中的矩阵类型转换

    我正在尝试使用滤波器对图像进行卷积 并借助 opencv 中的 filter2D 函数将其存储到 CV 64F 类型的矩阵中 但目标矩阵的类型发生了变化 我尝试借助 allocateTo 0r ConvertTo 函数将其更改回 CV 64
  • 了解随机起始权重对神经网络性能的影响

    使用 R 和包neuralnet 我尝试对数据进行建模 如下所示 这些是几天内以 10 分钟为间隔的温度读数 上面是 2 天的截图 使用下面的代码 我将神经网络拟合到数据 可能有更简单的方法来对这些精确数据进行建模 但将来数据可能看起来完全
  • 无需安装即可使用Python

    我有一个安装程序 它使用 Python 脚本来安装多个组件 我不想在用户计算机上安装 Python 如果用户还没有安装 Python 并且我也不希望安装 Python 成为使用我的安装程序的先决条件 有没有一种方法可以在不使用安装程序的情况
  • 范围之间的随机日期时间 - 不统一输出

    我实现了下面的 RandomDate 但我总是不断获取接近 From 日期的值 我可能在这里错过了一些东西 public static DateTime GetRandomDate DateTime from DateTime to var
  • 为什么矢量化通常比循环更快?

    为什么在执行操作的硬件的最低级别和所涉及的一般底层操作 即 运行代码时所有编程语言的实际实现通用的事情 矢量化通常比循环快得多 计算机在循环时会做什么而在使用矢量化时不会做什么 我指的是计算机执行的实际计算 而不是程序员编写的计算 或者它有
  • ChrisBanes PullToRefresh“正在加载...”问题

    我正在使用我发现的 chrisbanes 的 PullToRefresh ListViewhere https github com chrisbanes Android PullToRefresh 多亏了它的文档 我成功地实现了它 然而
  • 将 Spark Dataframe 保存到 Elasticsearch - 无法处理类型异常

    我设计了一个简单的作业 使用 Spark 从 MySQL 读取数据并将其保存在 Elasticsearch 中 这是代码 JavaSparkContext sc new JavaSparkContext new SparkConf setA
  • Three.js 中的文本几何

    我在 Three js r74 中的 TextGeometry 遇到一些问题 我该如何正确实施 这是我的代码笔 codepen io cheesyeyes pen eJqZxK 提前致谢 好吧 对于每个正在寻找简单答案而不是链接和其他超载示
  • 如何将数组写入 Google 电子表格?

    我正在构建一个具有整数值的数组 并尝试一次性将其写入 Google 电子表格 var myArray new Array for i 1 i lt 100 i myArray i i ss getRange 8 4 1 100 setVal
  • 如何通过连接到 Linux 计算机的 GSM/GPRS 调制解调器发送彩信? [关闭]

    很难说出这里问的是什么 这个问题是含糊的 模糊的 不完整的 过于宽泛的或修辞性的 无法以目前的形式得到合理的回答 如需帮助澄清此问题以便重新打开 访问帮助中心 help reopen questions 我有一个目录 其中包含 50 个图像
  • 9 补丁填充区域不适用于多个比例区域

    在我的 9 补丁中 我添加了一个填充区域 作为占据红色矩形下方大部分宽度的内容 然而 正如比例图像所示 它不起作用 如果我删除箭头所示的比例区域 那么它就可以完美工作 我做错了什么或者不理解 9 补丁吗 Thanks 我找到了一个相关的答案
  • Rails:form_for复选框设置为true或false,无论该框被选中/取消选中

    我有一个名为 users 的模型 它有 2 个布尔属性send email and send text 我有一个编辑用户模型的表单 我希望它根据该框是否选中 取消选中将这些属性设置为 true false 这是我的表格 div class
  • C# 泛型类型推断与协方差 - bug 或限制

    当具有依赖参数的泛型方法推断类型时 它在某些情况下会给出意外的结果 如果我明确指定类型 则一切都可以正常工作 无需任何进一步的更改 IEnumerable