2009-01-30 97 views
0

說我有這樣的方法(從之前的被盜用喬恩斯基特SO回答):垃圾收集在產量的方法

public static IEnumerable<TSource> DuplicatesBy<TSource, TKey> 
    (this IEnumerable<TSource> source, Func<TSource, TKey> keySelector) 
{ 
    HashSet<TKey> seenKeys = new HashSet<TKey>(); 
    foreach (TSource element in source) 
    { 
     // Yield it if the key hasn't actually been added - i.e. it 
     // was already in the set 
     if (!seenKeys.Add(keySelector(element))) 
     { 
      yield return element; 
     } 
    } 
} 

在這個方法中我有一個用於保存已鍵一個HashSet看到。如果我在這種情況下使用這種方法。

List<string> strings = new List<string> { "1", "1", "2", "3" }; 
List<string> somewhatUniques = strings.DuplicatesBy(s => s).Take(2); 

這隻會列舉字符串列表中的前2個項目。但垃圾收集如何收集seenKeys哈希集。由於yield只是暫停執行方法,如果方法很昂貴,我怎麼才能確保正確處理事情?

回答

1

編譯器生成一個隱藏類來實現此代碼。它有一個超級祕密的名字:「d__0`2」。您看到的關鍵字和源變量成爲該類的字段,確保它們無法收集垃圾,除非收集類對象。

該類實現IEnumerator接口,使用迭代器的客戶端代碼使用該接口來調用MoveNext()方法。這是保持類對象活着的接口引用。它保持它的領域活着。只要客戶端代碼完成foreach循環,接口引用就會消失,允許GC清除所有內容。

使用Ildasm.exe或Reflector自己查看。它會讓你對語法糖的隱藏成本有所瞭解。迭代器並不便宜。

1

好吧,垃圾收集不會收集它馬上。它顯然不能。在內部,當你對你的方法做一些類似foreach的事情時,它會很多次地調用GetEnumerator()和MoveNext()來獲得每一件事情。枚舉器是一次性的,當枚舉器被放置時 - foreach在循環結束時將其放置在你的頭上 - 垃圾收集器可以隨意清理你迭代器中的任何對象。因此,如果你的迭代器中有很多昂貴的狀態,並且你正在遍歷它很長一段時間,那麼你可能要麼不使用yield return,要麼通過調用某個東西來立即評估整個枚舉像ToArray()然後看那個。

編輯:那麼,在回答您的最後一個問題 - 如何可以確保它得到處理 - 沒有什麼特別的,你需要的,如果你使用LINQ或的foreach結構上它做的,因爲他們把通過他們平常的魔法照顧自己。如果您手動獲取枚舉器,請確保在完成時調用Dispose()或將其放入使用塊中。

+0

我不能相信框架將允許hashset坐在我的appdomain關閉。不是我的迭代器會長時間坐下,這是一個提出問題的人爲的例子。 – 2009-01-30 15:26:41