购买
下载掌阅APP,畅读海量书库
立即打开
畅读海量书库
扫码下载掌阅APP

建议18:foreach不能代替for

上一个建议中提到了foreach的两个优点:语法更简化,以及默认调用Dispose方法,所以我们强烈建议在实际的代码编写中更多地使用foreach。那么,问题出现了:该建议是否适合全部场景呢?答案显然是否定的。

foreach存在的一个问题是:它不支持循环时对集合进行增删操作。比如,运行下面的代码将会直接抛出异常InvalidOperationException:


List<int>list=new List<int>(){0,1,2,3};

foreach(int item in list)

{

list.Remove(item);

Console.WriteLine(item.ToString());

}


取而代之的方法是使用for循环:


List<int>list=new List<int>(){0,1,2,3};

for(int i=0;i<list.Count;i++)

{

list.Remove(list[i]);

Console.WriteLine(list[i].ToString());

}


foreach循环使用了迭代器进行集合的遍历,它在FCL提供的迭代器内部维护了一个对集合版本的控制。那么什么是集合版本?简单来说,其实它就是一个整型的变量,任何对集合的增删操作都会使版本号加1。foreach循环会调用MoveNext方法来遍历元素,在MoveNext方法内部会进行版本号的检测,一旦检测到版本号有变动,就会抛出InvalidOperationException异常。

如果使用for循环就不会带来这样的问题。for直接使用索引器,它不对集合版本号进行判断,所以不存在因为集合的变动而带来的异常(当然,超出索引长度这种情况除外)。

由于for循环和foreach循环在实现上有所不同(前者索引器,后者迭代器),因此关于两者性能上的争议从来没有停止过。但是,即使有争议,双方却都承认两者在时间和内存上存在损耗,尤其是针对泛型集合时,两者的损耗是在同一个数量级别上的。本建议不打算写测试代码来比较两者的效率,不妨撇开争议,来看看它们的具体实现到底是怎么样的。

以类型List<T>为例,索引器如下所示:


public T this[int index]

{

[TargetedPatchingOptOut

("Performance critical to inline across NGen image boundaries")]

get

{

if(index>=this._size)

{

ThrowHelper.ThrowArgumentOutOfRangeException();

}

return this._items[index];

}

}


迭代器如下所示:


public bool MoveNext()

{

List<T>list=this.list;

if((this.version==list._version)&&(this.index<list._size))

{

this.current=list._items[this.index];

this.index++;

return true;

}

return this.MoveNextRare();

}

public T Current

{

[TargetedPatchingOptOut("Performance critical to inline this type of

method across NGen image boundaries")]

get

{

return this.current;

}

}


可以看到,List<T>类型内部维护着一个泛型数组:


private T[]_items;


无论是for循环还是foreach循环,内部都是对该数组的访问,而迭代器仅仅是多进行了一次版本检测。事实上,在循环内部,两者生成的IL代码也是差不多的。但是,正如本建议刚开始提到的那样,因为版本检测的缘故,foreach循环并不能代替for循环。 b1WqLqFrHid2F7/rP6EnPwaCq2WvLLKKfZhAaeJrtJIHvA3F9qyZKqtUshyHPaxz

点击中间区域
呼出菜单
上一章
目录
下一章
×