1

假设我有这样的方法(从 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 哈希集。由于 yield 只是暂停了方法的执行,如果方法很昂贵,我怎么能确保我正确地处理东西?

4

2 回答 2

2

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

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

使用 Ildasm.exe 或 Reflector 亲自查看。它还将使您对语法糖的隐藏成本有所了解。迭代器并不便宜。

于 2009-01-30T19:27:52.363 回答
1

好吧,垃圾收集不会立即收集它。显然不能。

在内部,当您对方法执行诸如 foreach 之类的操作时,它会多次调用 GetEnumerator() 和 MoveNext() 以获取每件事。枚举器是一次性的,当枚举器被释放时——foreach 在循环结束时为你释放它——垃圾收集将随意清理迭代器中的任何对象。

因此,如果您的迭代器中有很多昂贵的状态,并且您要迭代很长时间,那么您可能不想使用 yield return,或者通过调用类似 ToArray() 的方法立即评估整个枚举然后看着那个。

编辑:所以,为了回答你的最后一个问题——你如何确保它被处理——如果你在上面使用 LINQ 或 foreach 构造,你不需要做任何特别的事情,因为他们自己通过他们的平常的魔法。如果您手动获取枚举器,请确保在完成后调用 Dispose() 或将其放入 using 块中。

于 2009-01-30T15:23:51.153 回答