Enumerator.MoveNext()的奇怪行为

时间:2015-07-23 15:05:03

标签: c# infinite-loop enumerator

有人可以解释为什么这段代码在无限循环中运行?为什么MoveNext()总是返回true

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

2 个答案:

答案 0 :(得分:41)

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

&#xA;&#xA;

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

&#xA;&#xA; < pre> while(x.TempList.MoveNext())&#xA; {&#xA; //忽略此&#xA;}&#xA; &#xA;&#xA;

这相当于:

&#xA;&#xA;
  while(true)&#xA; {&#xA; var tmp = x.TempList;&#xA; var result = tmp.MoveNext();&#xA; if(!result)&#xA; {&#XA;打破;&#XA; }&#XA;&#XA; //原始循环体&#xA;}&#xA;  
&#xA;&#xA;

现在注意我们所谓的 MoveNext() - 匿名类型值的副本。您实际上无法更改匿名类型中的值 - 您所拥有的只是一个可以调用的属性,它将为您提供值的副本。

&#xA;&#xA;

如果您将代码更改为:

&#xA;&#xA;
  var x = new {TempList =(IEnumerable&lt; int&gt;)new list&lt; int&gt; {1,3,6,9} .GetEnumerator()};&#xA;  
&#xA;&#xA;

...然后你最终会得到一个<匿名类型中的em> reference 。对包含可变值的框的引用。当你在该引用上调用 MoveNext()时,框内的值将会发生变异,所以它会做你想要的。

&#xA;&#xA;

有关非常类似情况的分析(再次使用 List&lt; T&gt; .GetEnumerator()),请参阅我的2010年博客文章”迭代,该死的你!“

&#xA;

答案 1 :(得分:3)

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

不幸的是,因为在foreach调用时没有类型可以提供值类型枚举器而没有通过GetEnumerator方法调用调用时提供值类型枚举器的方法,{{{{{{ 1}}面临性能与语义的轻微权衡。当时,因为C#不支持变量类型推断,所以使用从List<T>返回的值的任何代码都必须声明类型为List<T>.GetEnumeratorIEnumerator<T>的变量。使用前一种类型的代码就像List<T>.Enumerator是引用类型一样,并且可以假设使用后者的程序员意识到它是一种结构类型。然而,当C#添加类型推断时,该假设不再成立。代码可以很容易地使用类型List<T>.Enumerator而不需要程序员知道该类型的存在。

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