C# GC是如何与IEnumerator一起工作的?

C# GC是如何与IEnumerator一起工作的?,c#,garbage-collection,C#,Garbage Collection,我知道枚举数和yield关键字可以用来帮助异步/交错操作,因为您可以调用MoveNext()来运行下一个代码块 然而,我并不真正理解枚举器对象是什么。使用枚举器作用域的内存在哪里?如果您不一直使用枚举器,它最终会得到GC'd吗 基本上,我正在努力降低我的GC命中率,因为我可能会使用很多枚举数,而GC在Unity内部可能是一个问题,特别是由于它使用的是旧版本的Mono 我已经试着描述这个,但是我仍然无法将我的头缠绕在他们身上。我不理解枚举数的作用域/引用。我也不明白当你从一个函数中创建一个枚举数时

我知道枚举数和yield关键字可以用来帮助异步/交错操作,因为您可以调用
MoveNext()
来运行下一个代码块

然而,我并不真正理解枚举器对象是什么。使用枚举器作用域的内存在哪里?如果您不一直使用枚举器,它最终会得到GC'd吗

基本上,我正在努力降低我的GC命中率,因为我可能会使用很多枚举数,而GC在Unity内部可能是一个问题,特别是由于它使用的是旧版本的Mono

我已经试着描述这个,但是我仍然无法将我的头缠绕在他们身上。我不理解枚举数的作用域/引用。我也不明白当你从一个函数中创建一个枚举数时,枚举数是否被创建为对象

以下示例更好地显示了我的困惑:

// Example enumerator
IEnumerator<bool> ExampleFunction()
{
    SomeClass heavyObject = new SomeClass();
    while(heavyObject.Process())
    {
        yield return true;
    }

    if(!heavyObject.Success)
    {
        yield return false;
    }

    // In this example, we'll never get here - what happens to the incomplete Enumerator
    // When does heavyObject get GC'd?
    heavyObject.DoSomeMoreStuff();
}

// example call - Where does this enumerator come from? 
// Is something creating it with the new keyword in the background?
IEnumerator<bool> enumerator = ExampleFunction();
while(enumerator.MoveNext())
{
    if(!enumerator.Current)
    {
        break;
    }
}

// if enumerator is never used after this, does it get destroyed when the scope ends, or is it GC'd at a later date?
//示例枚举器
IEnumerator示例函数()
{
SomeClass heavyObject=新的SomeClass();
while(heavyObject.Process())
{
收益率返回真;
}
如果(!heavyObject.Success)
{
收益回报率为假;
}
//在这个例子中,我们永远不会得到这里-不完整的枚举数会发生什么情况
//heavyObject何时获得GC?
重对象。DoSomeMoreStuff();
}
//示例调用-此枚举器来自何处?
//有什么东西在后台用新关键字创建它吗?
IEnumerator枚举器=ExampleFunction();
while(枚举数.MoveNext())
{
如果(!enumerator.Current)
{
打破
}
}
//如果在此之后从未使用过枚举器,那么它是在作用域结束时被销毁,还是在以后的某个日期被GC销毁?

枚举数的管理方式与其他所有托管实例的管理方式相同-当它超出范围时。因此,如果从未调用
MoveNext()
,则当枚举数超出其范围时,GC会删除(或更好地将其标记为删除)。迭代枚举数不会以任何并行方式发生,因此执行和垃圾收集在使用完枚举数后都会以确定的顺序运行

在迭代器方法中,
yield return
-stament根本不是最后一条语句,它只是意味着调用
MoveNext()
时应返回当前结果。但是,
yield return
后面的所有内容都在调用
MoveNext
之后运行,因此您也可以调用
dosomemoretuff


但是,您的
heavyObject
具有该方法的作用域,因此它与迭代器一样长—迭代器是
while
循环中的块。这意味着,如果您的迭代器甚至没有返回任何实例,则将立即处理heavyObject,否则当迭代完成时。

您可能应该阅读一篇枚举器内部文章。从内部你可以回答所有这些问题

速成课程:每个迭代器方法执行返回一个新的枚举器对象。局部变量变成字段

如果没有人再使用该枚举器对象,则该对象符合收集条件。此外,局部变量创建的所有引用都是出于GC目的而消失的

当局部变量不再是GC引用时,存在一些边缘情况。如果您的枚举数是相当短暂的,这并不重要

CLR不知道什么是枚举数。它只是一个由C#编译器生成的类

将xanatos的链接拉到这个答案中,因为它是说明性的,而且他似乎没有发布答案:

//如果在此之后从未使用过枚举器,那么当 范围结束了

正确的


收集的资格是没有参考资料。对于GC的作用域,枚举数没有什么特别之处。枚举数与任何其他类型超出作用域时的处理/清除方式相同。

编译器重写枚举数方法,并将代码移动到具有无法说出的名称的类中。该类实现了一个状态机,其中局部变量成为该类的字段,允许在不丢失状态的情况下重新输入MoveNext()。IEnumerator接口引用实际上是对该类对象的引用。当引用被收集时,一切都会消失,只要代码离开foreach循环,就有资格被GC删除。Hans说的例子:好的,谢谢。这根本没有达到我的预期。这是否意味着与传统方法相比,枚举数相当重?我试图抑制GC,但似乎每当我启动一个新的枚举器(我有很多枚举器)时,我都会创建新的垃圾。在大量使用IEnumerable和IEnumerator的同时降低收集压力是一个棘手的问题,但有办法做到这一点。你应该问一个单独的问题,清楚地描述你的场景。您还应该仔细研究.NET库如何实现列表的枚举器;其设计目的是将收集压力降至最低。另外,如果你不明白foreach是如何进行duck类型模式匹配的,那么也请仔细阅读。嗯,不,这些都是非常小的对象,它们只有在foreach循环时才有一个引用。他们从gen#0中脱颖而出的唯一方法是循环进行大量分配。在这种情况下,小对象远离真实问题。专注于正确的问题,这不是问题。对,这解释了很多。我不知道automagic在统计人员方面做了多少工作。我有很多返回枚举数的方法,我有很多东西调用/使用它们。这听起来可能是一个GC问题,因为每个即将存在的枚举数都需要GCing。如果一个枚举数不是从任何地方引用的,而是从一个函数中本地引用的,那么它是否可以廉价/即时地获得GC?