在做一个项目时,我写了一个类似于下面的迭代器 block :
public class Sequence<T> : IEnumerable<T>
{
public T Head{get; private set;}
public Sequence<T> Tail {get; private set;}
public bool IsEmpty {get; private set;}
public IEnumerator<T> GetEnumerator()
{
Sequence<T> collection = this;
while (!collection.IsEmpty)
{
yield return collection.Head;
collection = collection.Tail;
}
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
如您所见,我预计在第二次调用 MoveNext
之后,GC 将能够收集原始集合,因为迭代器 block 不再持有对它的引用,只有它的尾部(如 collection = collection.Tail
中所示)。
然而,这并没有发生。我发现了编译器生成的 IEnumerator<T>
将始终持有对Sequence<T>
实例的引用创造了它。
为了证明这一点,我编写了以下迭代器 block 并检查了生成的 IL:
public IEnumerator<T> GetEnumerator()
{
yield return default(T);
}
令我惊讶的是,IL 等同于:
public IEnumerator<T> GetEnumerator()
{
var enumerator = new CompilerGeneratedEnumerator();
enumerator.this_field = this;
}
逐字记录:
.maxstack 2
.locals init (
[0] class Sequences.Sequence`1/'<GetEnumerator>d__3'<!T>
)
IL_0000: ldc.i4.0
IL_0001: newobj instance void class Sequences.Sequence`1/'<GetEnumerator>d__3'<!T>::.ctor(int32)
IL_0006: stloc.0
IL_0007: ldloc.0
IL_0008: ldarg.0
IL_0009: stfld class Sequences.Sequence`1<!0> class Sequences.Sequence`1/'<GetEnumerator>d__3'<!T>::'<>4__this'
IL_000e: ldloc.0
IL_000f: ret
通过查看 <GetEnumerator>d__3
的 IL , 似乎是 <>4__this
字段从未访问过。 那么为什么它会生成呢?为什么枚举器需要指向 Sequence<T>
的实例是什么创造了它?
我可以通过自己编写 IEnumerator<T>
来解决这个问题,但我仍然想知道为什么首先会发生这种情况。
如果你想自己编译这个,你可以从这里获取项目的源代码: https://github.com/dcastro/Sequences
这是原始的迭代器 block :
ISequence<T> sequence = this;
while (!sequence.IsEmpty)
{
yield return sequence.Head;
sequence = sequence.Tail;
}
最佳答案
从逻辑上讲,您的第一个方法应该 捕获this
。这一行:
Sequence<T> collection = this;
... 只会在第一次调用 MoveNext()
时执行,所以它确实需要捕获它,而且它只能在生成的代码中的实例变量中捕获它。编译器可以在其最终使用后显式将其清空,但通常这只是一种浪费。
现在你的第二个案例更有趣了。是的,为了完成该方法,它不需要引用 this
- 但如果您在调试器中,并且您在 yield return
语句上有一个断点,您会期望能够检查this
,因为您在实例方法中。因此,至少在具有调试信息且没有优化的构建中,我认为将 this
作为实例变量包含在内是合理的。在优化的构建中,不捕获 this
是有意义的(并且接受如果你正在调试一个不适合调试的构建,则有一些限制)但我想这只是编译器的优化作者认为不重要。
关于c# - 为什么编译器生成的 IEnumerator<T> 持有对创建它的实例的引用?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/23564913/