事实上,这是出于性能原因。 BCL团队在决定采用一种可疑和危险的做法来决定采用什么样的方式进行研究之前做了批次的研究:使用可变值类型。
你问为什么这不会导致拳击。这是因为C#编译器不会生成代码来将东西装入IEnumerable或IEnumerator的foreach循环中(如果它可以避免的话)!
当我们看到
foreach(X x in c)
,我们首先要做的是检查是否C有一个名为GetEnumerator方法。如果是这样,那么我们检查它返回的类型是否具有方法MoveNext和属性current。如果是这样,那么foreach循环完全是使用对这些方法和属性的直接调用生成的。只有在“模式”不能匹配的情况下,我们才会回头去寻找接口。
这有两个理想的效果。首先,如果集合是一个整数集合,但是在泛型类型被发明之前编写,那么它不会承担拳击Current对象的值并将其拆箱为int的装箱罚款。如果Current是一个返回int的属性,我们就使用它。
其次,如果枚举器是一个值类型,那么它不会将枚举器装箱到IEnumerator。
就像我说的,BCL团队在这方面做了大量的研究,发现绝大多数时候,分配和取消分配的惩罚足够大,因此值得把它作为一个值类型即使这样做可能会导致一些疯狂的错误。
例如,考虑一下:
struct MyHandle : IDisposable { ... }
...
using (MyHandle h = whatever)
{
h = somethingElse;
}
你会很正确地期望尝试变异小时,失败,的确如此。编译器检测到您正在尝试更改具有挂起处置的内容的值,并且这样做可能会导致需要处理的对象实际上不会被处置。
现在,假设你有:
struct MyHandle : IDisposable { ... }
...
using (MyHandle h = whatever)
{
h.Mutate();
}
这里会发生什么?如果h是一个只读字段,您可能会合理地认为编译器会执行它的操作:make a copy, and mutate the copy为了确保该方法不会丢弃需要处理的值中的东西。
然而,与我们有关的直觉冲突应该是什么在这里发生:无论它是
using (Enumerator enumtor = whatever)
{
...
enumtor.MoveNext();
...
}
我们希望做一个使用块内的MoveNext将移动枚举到下一个一个struct或一个ref类型。
不幸的是,今天的C#编译器有一个bug。如果您处于这种情况,我们会选择不一致的策略。今天的行为是:
不幸的是,该规范很少提供这方面的指导。显然有些事情因为我们做得不一致而被打破,但是要做的事情一点都不清楚。
别人过这个运行相关的页面: http://stackoverflow.com/questions/384511/enumerator-implementation-use-struct-or-class http://www.eggheadcafe.com/software/ aspnet/31702392/c-compiler-challenge - s.aspx – 2010-07-02 18:59:08