2016-01-13 66 views
10

IEnumerator/IEnumerable對於yield方法和getter似乎是一個類,因此分配在堆上。但是,其他.NET類型(如List<T>)特別返回struct枚舉器以避免無用的內存分配。從快速瀏覽C#深入後,我沒有看到爲什麼在這裏也不是這種情況。爲什麼編譯器生成的枚舉器「yield」不是結構體?

我錯過了什麼嗎?

+0

我剛剛意識到,既然返回類型是一個接口('IEnumerable'或'IEnumerator'),它[會得到盒裝](http://stackoverflow.com/questions/3032750/)無論如何,是嗎?在這種情況下,無法將方法更改爲返回明確類型的枚舉器(如[List does](https://msdn.microsoft.com/en-us/library/b0yss765(v = vs.110))。 ASPX))?由於它實現了接口,所以它的所有代碼引用都應該保留。 (IIRC,這是可行的,因爲'foreach' [通過模式檢測](https://blogs.msdn.microsoft.com/ericlippert/2011/06/30/following-the-pattern/))。 – Lazlo

回答

8

Servy正確回答你的問題 - 一個問題,你回答自己的評論:

我才意識到,由於返回類型是一個接口,它會得到反正盒裝,是這樣嗎?

沒錯。您的後續問題是:

無法將該方法更改爲返回明確類型的枚舉器(如List<T>一樣)?

所以在這裏你的想法是,用戶寫道:

IEnumerable<int> Blah() ... 

和編譯器實際上生成返回BlahEnumerable這是執行IEnumerable<int>一個結構的方法,但用適當的GetEnumerator等方法和屬性這使得foreach的「模式匹配」功能可以取消拳擊。

雖然這是一個看似合理的想法,但當您開始說謊某種方法的返回類型時,會遇到嚴重的困難。 特別是當謊言涉及更改該方法是否返回一個結構或引用類型。想想所有出問題的東西:

  • 假設該方法是虛擬的。它如何被覆蓋?虛擬重寫方法的返回類型必須完全匹配重寫的方法。 (並且類似地,該方法覆蓋另一種方法,該方法實現接口的方法,等等。)

  • 假設該方法被製作成代表Func<IEnumerable<int>>Func<T>T中是協變的,但協方差僅適用於參考類型的類型參數。代碼看起來像返回一個IEnumerable<T>,但事實上它返回的值類型與IEnumerable<T>不協方差兼容,只有分配兼容

  • 假設我們有void M<T>(T t) where T : class,我們叫M(Blah())。我們期望推斷TIEnumerable<int>,它通過了約束檢查,但是結構類型不是而是通過了約束檢查。

依此類推。你很快就會在Three's Company(我在這裏約會自己的男孩)的一集中快速地結束,在那裏一個小小的謊言最終成爲一場巨大的災難。所有這些都可以節省少量的採集壓力。不值得。

我注意到編譯器創建的實現以一種有趣的方式節省了採集壓力。 第一次時間GetEnumerator在返回的枚舉上被調用,枚舉變成本身變成枚舉數。第二次當然,狀態是不同的,所以它分配一個新的對象。由於99.99%的可能情景是一個給定的序列只被枚舉一次,這對收集壓力是一個很大的節省。

+0

感謝您的徹底解答!我正在挑選收集壓力,因爲我正在使用Unity早期的Mono運行時非代GC,因此我正在密切關注分配*。 – Lazlo

6

該類將只有永遠通過接口使用。如果它是一個結構體,它將在100%的時間內被裝箱,使其比使用一個類更有效率。

不能框它,因爲它是通過定義,無法使用的類型在編譯時間它不存在當你開始編譯代碼。

編寫IEnumerator的自定義實現時,您可以在編譯代碼之前公開實際的基礎類型,使其可以在不被裝箱的情況下使用。

相關問題