Enumerator.MoveNext()的奇怪行为

本文关键字:MoveNext Enumerator | 更新日期: 2023-09-27 18:04:11

有人能解释一下为什么这个代码在无限循环中运行吗?为什么MoveNext()总是返回true ?

var x = new { TempList = new List<int> { 1, 3, 6, 9 }.GetEnumerator() };
while (x.TempList.MoveNext())
{
  Console.WriteLine("Hello World");
}

Enumerator.MoveNext()的奇怪行为

List<T>.GetEnumerator()返回一个可变值类型(List<T>.Enumerator)。您将该值存储在匿名类型中。

现在,让我们看看它的作用:

while (x.TempList.MoveNext())
{
    // Ignore this
}

相当于:

while (true)
{
    var tmp = x.TempList;
    var result = tmp.MoveNext();
    if (!result)
    {
        break;
    }
    // Original loop body
}

现在注意我们调用MoveNext() on的复制的值,它是匿名类型的。你实际上不能改变匿名类型中的值——你所拥有的只是一个你可以调用的属性,它会给你一个值的副本。

如果您将代码更改为:

var x = new { TempList = (IEnumerable<int>) new List<int> { 1, 3, 6, 9 }.GetEnumerator() };

…然后,您将最终获得匿名类型中的引用。对包含可变值的框的引用。当你在这个引用上调用MoveNext()时,方框内的值会发生变化,所以它会做你想做的事情。

对于一个非常类似的情况的分析(再次使用List<T>.GetEnumerator()),请参阅我2010年的博客文章"迭代,该死的你!"

而c#中的foreach构造和VB中的For Each循环。. NET通常与实现IEnumerable<T>的类型一起使用,它们将接受任何包含GetEnumerator方法的类型,其返回类型提供了合适的MoveNext函数和Current属性。让GetEnumerator返回一个值类型,在很多情况下可以使foreach比返回IEnumerator<T>更有效地实现。

不幸的是,由于没有办法在从foreach调用类型时提供值类型枚举器,而在被GetEnumerator方法调用时又提供值类型枚举器,因此List<T>的作者面临着性能与语义之间的轻微权衡。当时,由于c#不支持变量类型推断,任何使用List<T>.GetEnumerator返回值的代码都必须声明IEnumerator<T>List<T>.Enumerator类型的变量。使用前一种类型的代码表现得好像List<T>.Enumerator是一个引用类型,而使用后一种类型的程序员可以假定意识到它是一个结构类型。然而,当c#添加了类型推断后,这个假设就不再成立了。代码很容易最终使用List<T>.Enumerator类型,而程序员却不知道该类型的存在。

如果c#曾经定义了一个struct-method属性,可以用来标记那些不应该在只读结构上调用的方法,并且如果List<T>.Enumerator使用了它,那么像你这样的代码在调用MoveNext时可能会正确地产生编译时错误,而不是产生虚假行为。但是,我知道没有特别的计划添加这样的属性。