为什么编译器生成的枚举器用于" yield"不是结构?

时间:2016-01-13 03:09:59

标签: c# struct heap ienumerable yield

IEnumerator / IEnumerable的{​​{1}} / yield List<T>方法和getter似乎是一个类,因此在堆上分配。但是,其他.NET类型(如struct)专门返回tableview枚举器以避免无用的内存分配。通过对 C#In Depth 帖子的快速概述,我认为没有理由说这也不是这里的情况。

我错过了什么吗?

2 个答案:

答案 0 :(得分:10)

Servy正确回答了你的问题 - 你在评论中自己回答的一个问题:

  

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

右。你的后续问题是:

  

无法更改方法以返回显式类型的枚举器(如List<T>那样)?

所以你的想法是用户写道:

IEnumerable<int> Blah() ...

并且编译器实际上生成了一个返回BlahEnumerable的方法,该方法是一个实现IEnumerable<int>的结构,但具有适当的GetEnumerator等方法和属性,允许“模式匹配”功能foreach来消除拳击。

虽然这是一个看似合理的想法,但当你开始撒谎一个方法的返回类型时,会遇到严重的困难。 特别是当谎言涉及改变方法是返回结构还是引用类型时。想想所有出错的事情:

  • 假设该方法是虚拟的。它怎么能被覆盖?虚拟覆盖方法的返回类型必须与重写方法完全匹配。 (同样适用于:该方法覆盖了另一个方法,该方法实现了一个接口方法,等等。)

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

  • 假设我们void M<T>(T t) where T : class,我们致电M(Blah())。我们希望推断TIEnumerable<int>,它通过约束检查,但结构类型通过约束检查。

等等。你很快就会出现在Three's Company的一集中(男孩,我在这里约会自己),小谎言最终会变成一场巨大的灾难。所有这一切都节省了少量的收集压力。不值得。

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

答案 1 :(得分:6)

该类才能通过界面使用。如果它是一个结构,它将被100%的时间装箱,使其 效率低于使用类。

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

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