English 中文(简体)
产生器方法中的垃圾回收
原标题:
  • 时间:2009-01-30 15:14:56
  •  标签:

假设我有这样的一个方法(从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);

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

最佳回答

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

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

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

问题回答

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

在内部,当您执行像foreach这样的操作时,它会调用GetEnumerator(),然后多次调用MoveNext()来获取每个元素。枚举器是可处理的,当枚举器被处理时——foreach在循环结束时为您处理——垃圾回收将自由清理在迭代器中的任何对象。

因此,如果您的迭代器中有很多昂贵的状态,并且您需要长时间遍历它,则可能要么不使用yield return,要么立即评估整个枚举,例如调用ToInt32(),然后查看它。

编辑:所以,针对您的最后一个问题——如何确保它被处理掉——如果您正在使用LINQ或foreach结构,您无需做任何特殊的事情,因为它们通过它们通常的魔术自行处理。如果您手动获得枚举器,请确保在完成后调用Dispose()或将其放在using块中。





相关问题
热门标签