建議18:foreach不能代替for
上一個(gè)建議中提到了foreach的兩個(gè)優(yōu)點(diǎn):語(yǔ)法更簡(jiǎn)單,默認(rèn)調(diào)用Dispose方法,所有我們強(qiáng)烈建議在實(shí)際的代碼編寫(xiě)中更多的使用foreach。但是,該建議也有不適合的場(chǎng)景。
foreach存在一個(gè)問(wèn)題:它不支持循環(huán)時(shí)對(duì)集合進(jìn)行增刪操作。比如,運(yùn)行下面代碼會(huì)拋出異常InvalidOperationException:
List<int> list=new List<int>(){0,1,2,3}; foreach (int item in list) { list.Remove(item); Console.WriteLine(item); }
取而代之的方法是使用for循環(huán)
for (int i = 0; i < list.Count; i++) { list.Remove(list[i]); Console.WriteLine(list[i]); }
foreach循環(huán)使用了迭代器進(jìn)行集合的遍歷,它在FCL提供的跌代替內(nèi)部維護(hù)了一個(gè)對(duì)集合版本的控制。那么什么是集合版本?簡(jiǎn)單來(lái)說(shuō),其實(shí)它就是一個(gè)整形的變量,任何對(duì)集合的增刪操作都會(huì)使版本號(hào)加1.foreach會(huì)調(diào)用MoveNext方法來(lái)遍歷元素,在MoveNext方法內(nèi)部會(huì)進(jìn)行版本號(hào)的檢測(cè),一旦檢測(cè)到版本號(hào)有變動(dòng),就會(huì)拋出InvalidOperationException異常。
如果使用for循環(huán)就不會(huì)帶來(lái)這樣的問(wèn)題。for直接使用索引器,它不對(duì)集合版本號(hào)進(jìn)行判斷,所以不會(huì)存在以為集合的變動(dòng)而帶來(lái)的異常(當(dāng)然,超出索引長(zhǎng)度這種異常情況除外)。
由于for循環(huán)和foreach循環(huán)實(shí)現(xiàn)上有所不同(前者索引器,后者迭代器),因此關(guān)于兩者性能上的爭(zhēng)議從來(lái)沒(méi)有停止過(guò)。但是,即使有爭(zhēng)議,雙方都承認(rèn)兩者在時(shí)間和內(nèi)存上有損耗,尤其是針對(duì)泛型集合時(shí),兩者的損耗是在同一個(gè)數(shù)量級(jí)別上的。
以類(lèi)型List<T>為例,索引器如下所示:
[__DynamicallyInvokable]public T this[int index]{ [TargetedPatchingOptOut("Performance critical to inline across NGen image boundaries"), __DynamicallyInvokable] get { if (index >= this._size) { ThrowHelper.ThrowArgumentOutOfRangeException(); } return this._items[index]; } [TargetedPatchingOptOut("Performance critical to inline across NGen image boundaries"), __DynamicallyInvokable] set { if (index >= this._size) { ThrowHelper.ThrowArgumentOutOfRangeException(); } this._items[index] = value; this._version++; }}
迭代器如下所示:
[__DynamicallyInvokable]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();}
[__DynamicallyInvokable]public T Current{ [__DynamicallyInvokable, TargetedPatchingOptOut("Performance critical to inline this type of method across NGen image boundaries")] get { return this.current; }}
可以看到,List<T>類(lèi)內(nèi)部維護(hù)著一個(gè)泛型數(shù)組:
PRivate T[] _items;
無(wú)論是for循環(huán)還是foreach循環(huán),內(nèi)部都是對(duì)該數(shù)組的訪問(wèn),而迭代器僅僅是多進(jìn)行了一次版本檢測(cè)。事實(shí)上,在循環(huán)內(nèi)部,兩者生成的IL代碼也是差不多的,但是,正如本建議剛開(kāi)始提到的那樣,因?yàn)榘姹緳z測(cè)的緣故,foreach循環(huán)并不能代替for循環(huán)。
轉(zhuǎn)自:《編寫(xiě)高質(zhì)量代碼改善C#程序的157個(gè)建議》陸敏技
新聞熱點(diǎn)
疑難解答
圖片精選
網(wǎng)友關(guān)注