为什么BCL Collections使用struct枚举器而不是类?

Elo*_*off 53 .net c# ienumerator base-class-library

我们都知道可变结构一般是邪恶的.我也非常确定因为IEnumerable<T>.GetEnumerator()返回类型IEnumerator<T>,结构会立即被装入引用类型,比它们只是引用类型的开销要多得多.

那么为什么在BCL泛型集合中,所有枚举器都是可变结构?当然必须有一个很好的理由.我唯一想到的就是可以轻松复制结构,从而在任意点保留枚举器状态.但是Copy()IEnumerator接口上添加一个方法本来就不那么麻烦了,所以我不认为这本身就是一个逻辑上的理由.

即使我不同意设计决定,我也希望能够理解它背后的原因.

Eri*_*ert 76

实际上,这是出于性能原因.在决定采用你正确称之为可疑和危险的做法之前,BCL团队对此进行了大量研究:使用可变值类型.

你问为什么这不会导致拳击.这是因为C#编译器不会生成代码,以便在foreach循环中将内容包装到IEnumerable或IEnumerator中,如果它可以避免它!

当我们看到

foreach(X x in c)
Run Code Online (Sandbox Code Playgroud)

我们要做的第一件事是检查c是否有一个名为GetEnumerator的方法.如果是,那么我们检查它返回的类型是否有方法MoveNext和属性current.如果是,则完全使用对这些方法和属性的直接调用生成foreach循环.只有当"模式"无法匹配时,我们才会回头寻找接口.

这有两个理想的效果.

首先,如果集合是说,整数的集合,但被写发明了泛型类型之前,那么它不采取拳击的电流值对象,然后拆箱它为int的装箱损失.如果Current是返回int的属性,我们只使用它.

其次,如果枚举数是值类型,那么它不会将枚举数打包到IEnumerator.

就像我说的,首创置业团队做这个了大量的研究,发现绝大多数的时间,分配的处罚和释放枚举数足够大,这是值得使其成为一个值类型,即使这样做会引起一些疯狂的错误.

例如,考虑一下:

struct MyHandle : IDisposable { ... }
...
using (MyHandle h = whatever)
{
    h = somethingElse;
}
Run Code Online (Sandbox Code Playgroud)

你会非常正确地期望尝试改变h失败,事实确实如此.编译器检测到您正在尝试更改具有挂起处置的内容的值,并且这样做可能会导致需要处理的对象实际上不会被处置.

现在假设你有:

struct MyHandle : IDisposable { ... }
...
using (MyHandle h = whatever)
{
    h.Mutate();
}
Run Code Online (Sandbox Code Playgroud)

这里发生了什么?如果h是一个只读字段,你可能会合理地期望编译器会做它所做的事情:制作一个副本,并改变副本以确保该方法不会丢弃需要处理的值中的东西.

然而,这与我们对这里应该发生的事情的直觉相冲突:

using (Enumerator enumtor = whatever)
{
    ...
    enumtor.MoveNext();
    ...
}
Run Code Online (Sandbox Code Playgroud)

我们期望在使用块内部执行MoveNext 会将枚举数移动到下一个,无论它是struct还是ref类型.

不幸的是,C#编译器今天有一个bug.如果您处于这种情况,我们会选择不一致的策略.今天的行为是:

  • 如果通过方法变异的值类型变量是正常的局部变量,则它会正常变异

  • 但是,如果它是一个吊装本地(因为它是一个封闭在一个匿名函数迭代器块的或可变的),则本地实际上作为只读字段,并确保在副本上的突变发生的齿轮产生的取过度.

不幸的是,该规范对此事提供的指导很少.显然有些东西被破坏了,因为我们做的不一致,但正确的做法是什么都不清楚.

  • 但现在怎么样?事情变得更好了吗? (3认同)
  • 我想知道为什么这个答案如此充分了解; 然后我看到是谁写的. (2认同)
  • @Backwards_Dave:这是正确的。编译器不需要调用“GetEnumerator”;需要生成枚举集合的代码。如果它可以在不调用 GetEnumerator 的情况下执行此操作,因为数组是一种非常特殊的类型,其行为 100% 已知,那么它可以选择这样做。 (2认同)

STO*_*STO 6

当在编译时已知结构类型并且通过接口调用方法很慢时,结构方法是内联的,因此答案是:由于性能原因.