为什么编译器生成的“yield”枚举器不是结构体

本文关键字:枚举 结构体 yield 编译器 为什么 | 更新日期: 2023-09-27 18:30:00

编译器

yield方法和getter生成的IEnumerator/IEnumerable实现似乎是一个类,因此被分配到堆上。但是,其他 .NET 类型(如 List<T>(专门返回struct枚举器,以避免无用的内存分配。从 C# 深入帖子的快速概述来看,我认为没有理由在这里不这样做。

我错过了什么吗?

为什么编译器生成的“yield”枚举器不是结构体

Servy 正确地回答了你的问题——你在评论中回答了这个问题:

我刚刚意识到,由于返回类型是一个接口,无论如何它都会被装箱,对吗?

右。您的后续问题是:

不能将该方法更改为返回显式类型的枚举器(就像List<T>一样(吗?

所以你在这里的想法是用户写:

IEnumerable<int> Blah() ...

编译器实际上生成了一个返回BlahEnumerable的方法,该方法是一个实现IEnumerable<int>的结构,但具有适当的GetEnumerator etc方法和属性,允许foreach的"模式匹配"功能省略装箱。

尽管这是一个合理的想法,但当您开始对方法的返回类型撒谎时,会涉及严重的困难。特别是当谎言涉及更改方法是否返回结构或引用类型时。 想想所有出错的事情:

  • 假设该方法是虚拟的。如何覆盖它?虚拟重写方法的返回类型必须与重写的方法完全匹配。(类似地:该方法重写另一个方法,该方法实现接口的方法,等等。

  • 假设该方法被制作成委托Func<IEnumerable<int>>Func<T>T是协变的,但协方差只适用于引用类型的类型参数。代码看起来像它返回了一个IEnumerable<T>但实际上它返回的值类型与IEnumerable<T>不兼容协方差,只兼容赋值

  • 假设我们有void M<T>(T t) where T : class,我们称之为M(Blah()).我们期望推断出TIEnumerable<int>,它通过了约束检查,但结构类型没有通过约束检查。

等等。 你很快就在《三人行》的一集中结束了(男孩,我在这里和自己约会(,一个小谎言最终变成了一场巨大的灾难。 所有这些都是为了节省少量的收集压力。不值得。

我注意到编译器创建的实现确实以一种有趣的方式节省了收集压力。第一次对返回的枚举对象调用GetEnumerator时,可枚举对象会将自身转换为枚举器。当然,第二次状态是不同的,所以它分配了一个新对象。由于 99.99% 可能的情况是给定序列只枚举一次,因此可以大大节省收集压力。

该类只能通过接口使用。 如果它是一个结构体,它将在 100% 的时间内被装箱,使其效率低于使用类。

你不能装箱它,因为根据定义,它不可能在编译时使用该类型,因为当你开始编译代码时它不存在

编写 IEnumerator 的自定义实现时,可以在编译代码之前公开实际的基础类型,从而可以在不加框的情况下使用它。