List<T> 實現(xiàn)了三個獲取迭代器方法得湘,一個是類自身的方法,兩個是顯示的接口實現(xiàn):
public List<T>.Enumerator GetEnumerator()
IEnumerator<T> IEnumerable<T>.GetEnumerator()
IEnumeraotr IEnumerable.GetEnumerator()
這三個方法的內(nèi)部實現(xiàn)都是一樣的:
return new List<T>.Enumerator(this);
那么問題來了闲礼,當我們寫下如下的 foreach#1時读宙,發(fā)生了什么?
foreach#1
List<TNode> listthings;
foreach(var thing in listthings)
想要清楚的解釋這個問題懈词,就涉及到 foreach 的機制蛇耀。當我們寫下 foreach(E e in C)時,首先檢查的是 C 是否有 GetEnumerator 方法坎弯,如果有纺涤,再檢查 GetEnumerator 返回的類型是否實現(xiàn)了 MoveNext 方法和 Current 屬性。如果有抠忘, foreach 將直接使用這些方法和屬性撩炊。 如果前面檢查的不成立,才會嘗試將 C 轉(zhuǎn)換成 IEnumerable崎脉。
然后我們會過來看上面的問題拧咳,List<T> 有 GetEnumerator 方法。那么它返回的 List<T>.Enumerator 是個什么東東呢囚灼?
public strcut Enumerator : IEnumerator<T>, IDisposable,
IEnumerator
首先它是個 struct骆膝,沒錯,value type灶体。 當你使用 List<T>時阅签,請時刻記住這一點。
public T Current {get;}
public bool MoveNext();
其次它符合了上面提到的檢查第二點赃春,所以 foreach愉快地采用了 listthings.GetEnumerator愉择。
為什么 Enumerator 是 struct?
為了效率劫乱,當你用完 foreach织中,Enumerator 便可自動銷毀锥涕,不用等到 GC。BCL 在做了大量研究之后采用了這一策略狭吼。
不過這一點既有好處层坠,又有很容易就踩的坑〉篌希坑是什么破花,沒錯,就是裝箱疲吸。
我們再來看看 foreach#2
foreach#2
var iter = listthings as IEnumerable;
foreach(var thingobj in iter)
var iter2 = listthings as IEnumerable<TNode>;
foreach(var thing in iter2)
將 List<T>.Enumerator 轉(zhuǎn)為 IEnumeraotr 時座每,就完成了裝箱。 當然平時我們不會直接做這個轉(zhuǎn)換摘悴,但寫如下的代碼卻是很 easy:
void PrintAddress(IEnumerable<Address> addresslist)
{
foreach(var adr in addresslist)
}
PrintAddress(List<Address>)
如此很難直覺的看出有裝箱問題峭梳。又比如寫起來很順手的 Linq: listOfAddress.First() 等等。
所以在性能要求高的地方蹂喻,應避免使用 IList葱椭,Linq. 直接使用 List,避免 GC 帶來的壓力口四。
關于List<T>.Enumerator的實現(xiàn)細節(jié)孵运,還有很多有意思的地方。請參考鏈接:
http://marcinjuraszek.com/2013/10/playing-around-with-listt-part-two-ienumerable-and-ienumerablet-implementation.html