Enumerator.MoveNext() 的奇怪行为

Strange behavior of Enumerator.MoveNext()

谁能解释一下为什么这段代码 运行 在无限循环中?为什么总是 MoveNext() return true

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

List<T>.GetEnumerator() returns a mutable value type (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() 上调用什么 - 匿名类型的值的 copy。您实际上无法更改匿名类型中的值 - 您所拥有的只是一个可以调用的 属性,它将为您提供该值的副本。

如果将代码更改为:

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

... 那么您最终会得到匿名类型的 reference。对包含可变值的框的引用。当您对该引用调用 MoveNext() 时,框中的值将发生变化,因此它会执行您想要的操作。

有关非常相似情况的分析(再次使用 List<T>.GetEnumerator()),请参阅 my 2010 blog post "Iterate, damn you!"

虽然 C# 中的 foreach 构造和 VB.NET 中的 For Each 循环通常与实现 IEnumerable<T> 的类型一起使用,但它们将接受任何包含GetEnumerator 方法,其 return 类型提供了合适的 MoveNext 函数和 Current 属性。 GetEnumerator return 值类型在许多情况下允许 foreach 比 returned IEnumerator<T>.[=29 更有效地实现=]

不幸的是,因为无法通过一种方式在从 foreach 调用时提供值类型枚举器而不在被 GetEnumerator 方法调用时提供值类型枚举器,作者List<T> 面临性能与语义的轻微权衡。当时,因为 C# 不支持变量类型推断,任何使用从 List<T>.GetEnumerator 编辑的值 return 的代码都必须声明类型为 IEnumerator<T>List<T>.Enumerator 的变量.使用前一种类型的代码表现得好像 List<T>.Enumerator 是引用类型,而使用后者的程序员可以假定意识到它是一种结构类型。但是,当 C# 添加类型推断时,该假设不再成立。代码很容易最终使用类型 List<T>.Enumerator 而程序员不知道该类型的存在。

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