for和foreach
foreach是只读的,如果在foreach中修改正在遍历的容器会抛出InvildOperation异常。这这句话对系统提供的容器有效。如果自定义的容器中实现了相应的机制也有效。可以用Reflector查看一下系统提供的一些容器像List、Dictionary,会发现其中有个成员_version,每一次对容器的修改都会让_version值增加,在容器的遍历器中会检查_version的值如果值发生改变就会抛出异常。这应该是为了多线程考虑,当然也不全是,在遍历的实现中会保存容器的尺寸、当前访问的位置等信息以对下一次访问进行合理性检查。如果对容器进行修改这些信息就会过时,对下一次访问的合理性检查就有可能失效。会带来意想不到的结果。
foreach的实现用到了遍历器,遍历器需要消耗额外的空间与CPU的计算能力,如果有装箱和拆箱就更费事了。当然有人会说有yield。但是为了让循环的每次都执行对应yield语句是否需要额外的数据结构,这会带来比遍历器更多的空间消耗。
for语句没有这些额外的机制,循环控制由编码者完全控制,所以可以在循环内部对遍历对象进行修改,只要能够保证逻辑不出错。for语句的内部实现上来说不会需要额外的空间和计算能力。这并不能说for语句比foreach更有效。需要根据情况来定。对于数组或者其它连续存储的数据结构,在循环内部使用索引器进行访问,一般会使用偏移进行定位,这确实比较高效。但是对于非连续存储的数据结构,由于使用索引器进行定位不是靠偏移,有额外的计算工作。当然也有人在for里边使用遍历器进行访问,这还不如使用foreach。
所以能用foreach就用foreach,必须用for的时候才能用for。foreach简单易用性能损失可以忽略,如果为了提高性能,可以对算法进行优化,不用在这上面打主意。如果您已经到了不能忍受CLR带来的性能损失时请拒绝CLR。
使用foreach是注意隐含的修改容器类容的情况。
请看这样一段代码
public void CloseAllPages()
{
PageForm[] pageA = _visiblePageList.ToArray();
for(int i = 0; i < pageA.Length; i++)
{
pageA[i].Close();
}
}
_visiblePageList是一个浏览器页面窗口链表。在这里就不能使用foreach。
在页面文件关闭时会将自身从页面窗口链表中移除。这里就存在隐含修改容器内容的情况。
事件驱动中有很多隐含的情形,如何理解并对事件加以合理利用将会大大提高效率。如果不习惯事件驱动,这些隐含操作将会带来巨大的麻烦。