收益率中的垃圾收集方法

时间:2009-01-30 15:14:57

标签: c# .net garbage-collection yield

说我有这样的方法(从Jon Skeet先前的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);

这只会枚举字符串列表中的前两项。但是垃圾收集如何收集seenKeys hashset。由于yield只是暂停了方法的执行,如果方法很昂贵,我怎样才能确保正确处理?

2 个答案:

答案 0 :(得分:2)

编译器生成一个隐藏类来实现此代码。它有一个超级秘密的名字:“d__0`2”。您的seenKeys和源变量成为该类的字段,确保除非收集类对象,否则它们无法收集垃圾。

该类实现IEnumerator&lt;&gt;接口,使用迭代器的客户端代码使用该接口来调用MoveNext()方法。正是该接口引用使类对象保持活动状态。这使得它的领域保持活力。一旦客户端代码完成foreach循环,接口引用就会消失,允许GC清理所有内容。

使用Ildasm.exe或Reflector自行查看。它将为您提供一些有关语法糖隐藏成本的见解。迭代器并不便宜。

答案 1 :(得分:1)

好吧,垃圾收集不会马上收集 。它显然不可能。

在内部,当你对方法执行类似foreach的操作时,它会多次调用GetEnumerator()然后调用MoveNext()来获取每个东西。枚举器是一次性的,当枚举器被丢弃时 - foreach在循环结束时为你处理它 - 垃圾收集可以随意清理迭代器中的任何对象。

所以,如果你的迭代器中有很多昂贵的状态并且你在它上面迭代了很长时间,那么你可能想要不使用yield return,或者通过调用类似的东西立即评估整个枚举ToArray(),然后看着它。

编辑:所以,回答你的最后一个问题 - 如何确保它被处理掉 - 如果你在它上面使用LINQ或foreach结构,你就没什么特别的了因为他们通过他们通常的魔法自己照顾它。如果您手动获取枚举器,请确保在完成后调用Dispose()或将其放入使用块中。