回 帖 发 新 帖 刷新版面

主题:[原创]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。

回复列表 (共1个回复)

沙发

使用foreach是注意隐含的修改容器类容的情况。
请看这样一段代码
        public void CloseAllPages()
        {
            PageForm[] pageA = _visiblePageList.ToArray();
            for(int i = 0; i < pageA.Length; i++)
            {
                pageA[i].Close();
            }
        }
_visiblePageList是一个浏览器页面窗口链表。在这里就不能使用foreach。
在页面文件关闭时会将自身从页面窗口链表中移除。这里就存在隐含修改容器内容的情况。

我来回复

您尚未登录,请登录后再回复。点此登录或注册