2009-01-30 63 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