为什么编译器生成的“yield”枚举器不是结构体
本文关键字:枚举 结构体 yield 编译器 为什么 | 更新日期: 2023-09-27 18:30:00
为yield
方法和getter生成的IEnumerator
/IEnumerable
实现似乎是一个类,因此被分配到堆上。但是,其他 .NET 类型(如 List<T>
(专门返回struct
枚举器,以避免无用的内存分配。从 C# 深入帖子的快速概述来看,我认为没有理由在这里不这样做。
我错过了什么吗?
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())
.我们期望推断出T
是IEnumerable<int>
,它通过了约束检查,但结构类型没有通过约束检查。
等等。 你很快就在《三人行》的一集中结束了(男孩,我在这里和自己约会(,一个小谎言最终变成了一场巨大的灾难。 所有这些都是为了节省少量的收集压力。不值得。
我注意到编译器创建的实现确实以一种有趣的方式节省了收集压力。第一次对返回的枚举对象调用GetEnumerator
时,可枚举对象会将自身转换为枚举器。当然,第二次状态是不同的,所以它分配了一个新对象。由于 99.99% 可能的情况是给定序列只枚举一次,因此可以大大节省收集压力。
该类只能通过接口使用。 如果它是一个结构体,它将在 100% 的时间内被装箱,使其效率低于使用类。
你不能不装箱它,因为根据定义,它不可能在编译时使用该类型,因为当你开始编译代码时它不存在。
编写 IEnumerator
的自定义实现时,可以在编译代码之前公开实际的基础类型,从而可以在不加框的情况下使用它。