C# 当使用;“收益率”;为什么编译器生成的类型同时实现IEnumerable和IEnumerator

C# 当使用;“收益率”;为什么编译器生成的类型同时实现IEnumerable和IEnumerator,c#,.net,linq,garbage-collection,yield-return,C#,.net,Linq,Garbage Collection,Yield Return,我们试图使用IEnumerable作为工厂,在每次迭代时生成不同的对象。这些应该尽快进行GC'ed。但是请注意,我们保留了对枚举数的引用,以便可以再次调用它。所以我们的计划基本上是这样的: public class YieldSpec { public static IEnumerable<string> Strings() { yield return "AAA"; yield return "BBB"; yield

我们试图使用
IEnumerable
作为工厂,在每次迭代时生成不同的对象。这些应该尽快进行GC'ed。但是请注意,我们保留了对枚举数的引用,以便可以再次调用它。所以我们的计划基本上是这样的:

public class YieldSpec
{
    public static IEnumerable<string> Strings()
    {
        yield return "AAA";
        yield return "BBB";
        yield return "CCC";
    } 
    public void YieldShouldAllowGC()
    {
        var e = Strings();
        foreach (var a in e)
        {
            Console.WriteLine(a);
        }

    }
}
公共类YieldSpec
{
公共静态IEnumerable字符串()
{
收益率“AAA”;
收益率回报率“BBB”;
收益率“CCC”;
} 
公共空间收益率应为allowgc()
{
var e=Strings();
foreach(e中的var a)
{
控制台写入线(a);
}
}
}
在调试器中查看该代码:

您可以看到,当断点被命中时,IEnumerable有一个对“CCC”的引用


这不应该真的发生。IEnumerable应仅在调用GetEnumerator时生成IEnumerator。这是IEnumerable可以包含状态的预期行为吗?

作为性能原因的实现细节,是的,状态机同时实现了
IEnumerable
IEnumerator
。也就是说,正确地做到这一点已经足够聪明了。这只是
IEnumerable
第一次被要求提供一个
IEnumerator
返回它自己。以后对
GetEnumerator
的任何调用都会导致创建对象的新实例,这样就可以维护单独的迭代器状态。之所以这样做,是因为尽管能够创建多个
IEnumerable
IEnumerators很重要,但绝大多数实际情况都只涉及创建一个IEnumerators,因此这是优化的情况。

我无法重现它。确定已处理所有实例?使用。您是否删除了要测试的实例?它不会处理整个内容,直到您处理了
Test
。(它是惰性的)如果范围离开
Test()
(或者更具体地说,在
foreach()
之后)将使枚举器符合垃圾收集的条件。有什么问题?在调用
Test()
之后,GC还没有启动?请将您的代码粘贴到问题中,而不是屏幕截图。对此表示抱歉,但我想显示手表的输出以及我有断点的事实。还添加了pastable代码,这澄清了我在运行调试器时看到的一些混乱。我多次运行IEnumerable,但只在第一次运行时看到当前属性被更新。这就清楚了。@bradgonesurfing:为了澄清这个正确答案,这里的“性能原因”是降低GC压力。您可以很容易地想象这样一种情况:这些包含
的方法每秒调用数千次并立即枚举。如果我们能将创建的新对象数量减少一半,这显然是一个胜利。嗨@EricLippert我完全理解其中的道理,这似乎是一个完美的权衡。这让我大吃一惊,因为我通过分析发现了一大堆我认为应该是GC的对象。在我的例子中,它是WPF上下文菜单的大型继承者的根节点。我宁愿他们得到GC而不是被IEnumerable持有。我的案子是个离群的案子,我会想办法解决的。