IEnumerator
/ IEnumerable
的{{1}} / yield
List<T>
方法和getter似乎是一个类,因此在堆上分配。但是,其他.NET类型(如struct
)专门返回tableview
枚举器以避免无用的内存分配。通过对 C#In Depth 帖子的快速概述,我认为没有理由说这也不是这里的情况。
我错过了什么吗?
答案 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())
。我们希望推断T
是IEnumerable<int>
,它通过约束检查,但结构类型不通过约束检查。
等等。你很快就会出现在Three's Company的一集中(男孩,我在这里约会自己),小谎言最终会变成一场巨大的灾难。所有这一切都节省了少量的收集压力。不值得。
我注意到编译器创建的实现以一种有趣的方式保存了收集压力。在返回的可枚举项上调用GetEnumerator
的第一个时间,可枚举将本身转换为枚举器。当然第二次状态是不同的,所以它分配一个新的对象。由于99.99%可能的情况是给定的序列只被枚举一次,这可以大大节省收集压力。
答案 1 :(得分:6)
该类仅才能通过界面使用。如果它是一个结构,它将被100%的时间装箱,使其 效率低于使用类。
你不能不将它包装起来,因为根据定义,它不可能在编译时使用该类型它不存在当你开始编译代码时。
编写IEnumerator
的自定义实现时,您可以在编译代码之前公开实际的基础类型,允许在不加框的情况下使用它。