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");
}
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
时可能会正确地产生编译时错误,而不是产生虚假行为。但是,我知道没有特别的计划添加这样的属性。