1. 程式人生 > >【轉】編寫高質量代碼改善C#程序的157個建議——建議18:foreach不能代替for

【轉】編寫高質量代碼改善C#程序的157個建議——建議18:foreach不能代替for

aries 不同 針對 一次 help 停止 pre ica tof

建議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