標籤:style exception 迭代器 ati div orm items 索引 操作
建議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); }
取而代之的方法是使用for迴圈
for (int i = 0; i < list.Count; i++) { list.Remove(list[i]); Console.WriteLine(list[i]); }
foreach迴圈使用了迭代器進行集合的遍曆,它在FCL提供的跌代替內部維護了一個對集合版本的控制。那麼什麼是集合版本?簡單來說,其實它就是一個整形的變數,任何對集合的增刪操作都會使版本號碼加1.foreach會調用MoveNext方法來遍曆元素,在MoveNext方法內部會進行版本號碼的檢測,一旦檢測到版本號碼有變動,就會拋出InvalidOperationException異常。
如果使用for迴圈就不會帶來這樣的問題。for直接使用索引器,它不對集合版本號碼進行判斷,所以不會存在以為集合的變動而帶來的異常(當然,超出索引長度這種異常情況除外)。
由於for迴圈和foreach迴圈實現上有所不同(前者索引器,後者迭代器),因此關於兩者效能上的爭議從來沒有停止過。但是,即使有爭議,雙方都承認兩者在時間和記憶體上有損耗,尤其是針對泛型集合時,兩者的損耗是在同一個數量層級上的。
以類型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>類內部維護著一個泛型數組:
private T[] _items;
無論是for迴圈還是foreach迴圈,內部都是對該數組的訪問,而迭代器僅僅是多進行了一次版本檢測。事實上,在迴圈內部,兩者產生的IL代碼也是差不多的,但是,正如本建議剛開始提到的那樣,因為版本檢測的緣故,foreach迴圈並不能代替for迴圈。
轉自:《編寫高品質代碼改善C#程式的157個建議》陸敏技
【轉】編寫高品質代碼改善C#程式的157個建議——建議18:foreach不能代替for