C# 为什么编译器生成IEnumerator<;T>;是否保留对创建它的实例的引用?

C# 为什么编译器生成IEnumerator<;T>;是否保留对创建它的实例的引用?,c#,.net,garbage-collection,ienumerable,C#,.net,Garbage Collection,Ienumerable,在处理项目时,我编写了一个迭代器块,如下所示: 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> G

在处理项目时,我编写了一个迭代器块,如下所示:

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();
    }
}
令我惊讶的是,IL相当于:

public IEnumerator<T> GetEnumerator()
{
    var enumerator = new CompilerGeneratedEnumerator();
    enumerator.this_field = this;
}
public IEnumerator GetEnumerator()
{
var枚举器=新编译器生成枚举器();
enumerator.this_字段=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
.maxstack 2
.init(
[0]类序列。序列'1/'d__\u 3'
)
IL_0000:ldc.i4.0
IL_0001:newobj实例无效类序列。序列'1/'d_3'::.ctor(int32)
IL_0006:stloc.0
IL_0007:ldloc.0
IL_0008:ldarg.0
IL_0009:stfld类序列。序列'1类序列。序列'1/'d_3'::'4_this'
IL_000e:ldloc.0
IL_000f:ret
通过查看
d_u3
的IL,似乎从未访问过
4_uthis
字段那么为什么会生成它呢?为什么枚举器需要指向创建它的
Sequence
实例?

我可以通过编写自己的
IEnumerator
来解决这个问题,但我仍然想知道为什么会发生这种情况


如果您想自己编译此文件,可以从以下位置获取项目的源代码:

这是最初的迭代器块:

ISequence<T> sequence = this;

while (!sequence.IsEmpty)
{
    yield return sequence.Head;
    sequence = sequence.Tail;
}
ISequence sequence=this;
而(!sequence.IsEmpty)
{
收益率-收益率序列;
sequence=sequence.Tail;
}

从逻辑上讲,您的第一个方法应该捕获此
。这一行:

Sequence<T> collection = this;
Sequence collection=this;
。。。将仅在第一次调用
MoveNext()
时执行,因此它确实需要捕获它,并且它只能在生成的代码中的实例变量中捕获它。编译器可以在最终使用后显式地将其清空,但通常这只是浪费


现在你的第二个案例更有趣了。是的,为了完成该方法,它不需要对
this
的引用-但是如果您在调试器中,并且在
yield return
语句上有一个断点,那么您应该能够检查
this
,因为您在实例方法中。因此,至少在一个包含调试信息而没有优化的构建中,我认为将
这个
作为一个实例变量是合理的。在优化的构建中,捕获<代码> > 将是有意义的(并且接受,如果调试一个不用于调试的构建,则存在一些限制),但我猜想这只是编译器作者认为不重要的优化。

< P>如果在迭代之后,原始项是GLoad是很重要的,您可以自己实现一个,而不是让编译器为您生成一个

以下是编译,但可能需要改进:

public class MyCollection<T> : IEnumerable<T>
{
    private T Head;
    private MyCollection<T> Tail;
    private bool IsEmpty;

    private class ThisEnumerator : IEnumerator<T>
    {
        public ThisEnumerator(MyCollection<T> toIterate)
        {
            _innerCurrent = toIterate;
        }
        private MyCollection<T> _innerCurrent;
        private bool _hasMoved = false;
        public T Current
        {
            get
            {
                return _innerCurrent.Head;
            }
        }

        object IEnumerator.Current
        {
            get
            {
                return this.Current;
            }
        }

        public void Dispose()
        {
            _innerCurrent = null;
        }

        public bool MoveNext()
        {
            if (_hasMoved)
            {
                _innerCurrent = _innerCurrent.Tail;
            }
            else
            {
                _hasMoved = true;
            }
            return !_innerCurrent.IsEmpty;
        }

        public void Reset()
        {
            throw new NotSupportedException();
        }
    }

    public IEnumerator<T> GetEnumerator()
    {
        return new ThisEnumerator(this);
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return this.GetEnumerator();
    }
}
公共类MyCollection:IEnumerable
{
私人T型头;
私人收集尾;
私人住宅是空的;
私有类ThisEnumerator:IEnumerator
{
公共此枚举器(MyCollection to Terate)
{
_内部电流=t电流;
}
私有MyCollection\u-innerCurrent;
private boolu hasMoved=false;
公共电流
{
得到
{
返回_innerCurrent.Head;
}
}
对象IEnumerator.Current
{
得到
{
返回此.Current;
}
}
公共空间处置()
{
_内部电流=零;
}
公共图书馆
{
如果(_已移动)
{
_innerCurrent=_innerCurrent.Tail;
}
其他的
{
_hasMoved=true;
}
return!\u innerCurrent.IsEmpty;
}
公共无效重置()
{
抛出新的NotSupportedException();
}
}
公共IEnumerator GetEnumerator()
{
返回新的ThisEnumerator(this);
}
IEnumerator IEnumerable.GetEnumerator()
{
返回此.GetEnumerator();
}
}

至于“为什么”的问题,正如我在一篇评论中指出的,我只是认为大多数迭代器(和
async
块,它们共享很多这种机制)都需要访问
这个

,如果你给出一个编译过的示例,那会很有帮助-代码不会,目前,大多数迭代器将需要引用它们所属的类的成员,因此编译器不执行特殊分析以确定在某些奇怪的情况下,
在迭代过程中不使用此
,这也许是可以原谅的。您不必使用迭代器来实现
IEnumerable
(其中“该代码”是第一个示例,当然是第二个示例。)@JonSkeet我编辑了我的文章,使示例代码与原始代码更为相似。我还添加了一个到项目源代码的链接,以便您可以自己编译它。谢谢。@dcastro:它仍然没有编译,这很令人沮丧——我真的不需要在这里获取整个项目。。。要想出一个简短但完整的例子并不难。希望我的回答已经解决了这两种情况的原因。谢谢Damien。我确实在我的帖子里说过,我写的东西和你的非常相似,我完全同意你的观点。在我的第一个示例中,我忽略了一个事实,即枚举器确实需要引用this
this
,也许我对编译器的期望太高了。在最后一次使用后将
null
分配给
4\u这个
并不是一个明显的优化。@dcastro:如果这是一个安慰的话,我似乎记得不久前我发现了一个更令人担忧的内存泄漏。。。我记不起细节了,但有些情况很糟糕。它们可能在以后的版本中已被修复。值得注意的是,第一次调用迭代器的
GetEnumerator()
方法时,它将重新启动
public class MyCollection<T> : IEnumerable<T>
{
    private T Head;
    private MyCollection<T> Tail;
    private bool IsEmpty;

    private class ThisEnumerator : IEnumerator<T>
    {
        public ThisEnumerator(MyCollection<T> toIterate)
        {
            _innerCurrent = toIterate;
        }
        private MyCollection<T> _innerCurrent;
        private bool _hasMoved = false;
        public T Current
        {
            get
            {
                return _innerCurrent.Head;
            }
        }

        object IEnumerator.Current
        {
            get
            {
                return this.Current;
            }
        }

        public void Dispose()
        {
            _innerCurrent = null;
        }

        public bool MoveNext()
        {
            if (_hasMoved)
            {
                _innerCurrent = _innerCurrent.Tail;
            }
            else
            {
                _hasMoved = true;
            }
            return !_innerCurrent.IsEmpty;
        }

        public void Reset()
        {
            throw new NotSupportedException();
        }
    }

    public IEnumerator<T> GetEnumerator()
    {
        return new ThisEnumerator(this);
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return this.GetEnumerator();
    }
}