C# 产量方法中的垃圾收集

C# 产量方法中的垃圾收集,c#,.net,garbage-collection,yield,C#,.net,Garbage Collection,Yield,假设我有这样一种方法(从Jon Skeet之前的SO答案中盗取): 公共静态IEnumerable Duplicates by (此IEnumerable源,Func键选择器) { HashSet seenKeys=新HashSet(); foreach(源中的TSource元素) { //如果没有实际添加密钥,则放弃它-即 //已经在片场了 如果(!seenKeys.Add(键选择器(元素))) { 收益-收益要素; } } } 在这个方法中,我有一个HashSet,用于保存已看到的键。如果

假设我有这样一种方法(从Jon Skeet之前的SO答案中盗取):

公共静态IEnumerable Duplicates by
(此IEnumerable源,Func键选择器)
{
HashSet seenKeys=新HashSet();
foreach(源中的TSource元素)
{
//如果没有实际添加密钥,则放弃它-即
//已经在片场了
如果(!seenKeys.Add(键选择器(元素)))
{
收益-收益要素;
}
}
}
在这个方法中,我有一个HashSet,用于保存已看到的键。如果我在这样的事情中使用这个方法

List<string> strings = new List<string> { "1", "1", "2", "3" };
List<string> somewhatUniques = strings.DuplicatesBy(s => s).Take(2);
List strings=新列表{“1”、“1”、“2”、“3”};
列出somewhatUniques=strings.DuplicatesBy(s=>s).Take(2);

这将仅枚举字符串列表中的前2项。但是垃圾收集是如何收集seenKeys哈希集的呢。由于yield只是暂停该方法的执行,如果该方法很昂贵,我如何确保正确地处理这些东西?

嗯,垃圾收集不会立即收集它。显然,这是不可能的

在内部,当您对方法执行类似于foreach的操作时,它会调用GetEnumerator(),然后多次对其执行MoveNext(),以获取每一项。枚举数是一次性的,当枚举数被释放时(foreach在循环结束时为您处理它),垃圾收集将可以随意清理迭代器中的任何对象

因此,如果您的迭代器中有很多昂贵的状态,并且您对其进行了很长时间的迭代,那么您可能希望不使用yield-return,或者通过调用ToArray()之类的函数立即计算整个枚举,然后查看它


编辑:因此,在回答您的最后一个问题——您如何确保它得到处理——如果您在它上面使用LINQ或foreach构造,您不需要做什么特别的事情,因为它们通过它们通常的魔法自行处理它。如果手动获取枚举数,请确保在完成后对其调用Dispose(),或将其放入using块。

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

该类实现IEnumerator接口,即使用迭代器的客户端代码使用该接口调用MoveNext()方法。正是接口引用使类对象保持活动状态。这让它的田地生机勃勃。一旦客户机代码完成foreach循环,接口引用就会消失,从而允许GC清理所有内容


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

我不敢相信框架会允许hashset在我的appdomain关闭之前一直闲置。并不是说我的迭代器会坐在那里很长时间,而是问这个问题的一个人为的例子。对不起,我可能不清楚。它不会让它永远坐在那里;它让它一直坐着,直到枚举器消失。
List<string> strings = new List<string> { "1", "1", "2", "3" };
List<string> somewhatUniques = strings.DuplicatesBy(s => s).Take(2);