事實上,這是出於性能原因。 BCL團隊在決定採用一種可疑和危險的做法來決定採用什麼樣的方式進行研究之前做了批次的研究:使用可變值類型。
你問爲什麼這不會導致拳擊。這是因爲C#編譯器不會生成代碼來將東西裝入IEnumerable或IEnumerator的foreach循環中(如果它可以避免的話)!
當我們看到
foreach(X x in c)
,我們首先要做的是檢查是否C有一個名爲GetEnumerator方法。如果是這樣,那麼我們檢查它返回的類型是否具有方法MoveNext和屬性current。如果是這樣,那麼foreach循環完全是使用對這些方法和屬性的直接調用生成的。只有在「模式」不能匹配的情況下,我們纔會回頭去尋找接口。
這有兩個理想的效果。首先,如果集合是一個整數集合,但是在泛型類型被髮明之前編寫,那麼它不會承擔拳擊Current對象的值並將其拆箱爲int的裝箱罰款。如果Current是一個返回int的屬性,我們就使用它。
其次,如果枚舉器是一個值類型,那麼它不會將枚舉器裝箱到IEnumerator。
就像我說的,BCL團隊在這方面做了大量的研究,發現絕大多數時候,分配和取消分配的懲罰足夠大,因此值得把它作爲一個值類型即使這樣做可能會導致一些瘋狂的錯誤。
例如,考慮一下:
struct MyHandle : IDisposable { ... }
...
using (MyHandle h = whatever)
{
h = somethingElse;
}
你會很正確地期望嘗試變異小時,失敗,的確如此。編譯器檢測到您正在嘗試更改具有掛起處置的內容的值,並且這樣做可能會導致需要處理的對象實際上不會被處置。
現在,假設你有:
struct MyHandle : IDisposable { ... }
...
using (MyHandle h = whatever)
{
h.Mutate();
}
這裏會發生什麼?如果h是一個只讀字段,您可能會合理地認爲編譯器會執行它的操作:make a copy, and mutate the copy爲了確保該方法不會丟棄需要處理的值中的東西。
然而,與我們有關的直覺衝突應該是什麼在這裏發生:無論它是
using (Enumerator enumtor = whatever)
{
...
enumtor.MoveNext();
...
}
我們希望做一個使用塊內的MoveNext將移動枚舉到下一個一個struct或一個ref類型。
不幸的是,今天的C#編譯器有一個bug。如果您處於這種情況,我們會選擇不一致的策略。今天的行爲是:
不幸的是,該規範很少提供這方面的指導。顯然有些事情因爲我們做得不一致而被打破,但是要做的事情一點都不清楚。
別人過這個運行相關的頁面: http://stackoverflow.com/questions/384511/enumerator-implementation-use-struct-or-class http://www.eggheadcafe.com/software/ aspnet/31702392/c-compiler-challenge - s.aspx – 2010-07-02 18:59:08