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.如果您处于这种情况,我们会选择不一致的策略.今天的行为是:
如果通过方法变异的值类型变量是正常的局部变量,则它会正常变异
但是,如果它是一个吊装本地(因为它是一个封闭在一个匿名函数迭代器块的或可变的),则本地被实际上作为只读字段,并确保在副本上的突变发生的齿轮产生的取过度.
不幸的是,该规范对此事提供的指导很少.显然有些东西被破坏了,因为我们做的不一致,但正确的做法是什么都不清楚.
归档时间: |
|
查看次数: |
4853 次 |
最近记录: |