有人可以解释为什么这段代码在无限循环中运行?为什么MoveNext()
总是返回true
?
var x = new { TempList = new List<int> { 1, 3, 6, 9 }.GetEnumerator() };
while (x.TempList.MoveNext())
{
Console.WriteLine("Hello World");
}
答案 0 :(得分:41)
List&lt; T&gt; .GetEnumerator()
返回一个可变值类型( List&lt; T&gt; .Enumerator
)。您将该值存储在匿名类型中。
现在,我们来看看它的作用:
&#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; var x = new {TempList =(IEnumerable&lt; int&gt;)new list&lt; int&gt; {1,3,6,9} .GetEnumerator()};&#xA;
&#xA;&#xA; ...然后你最终会得到一个<匿名类型中的em> reference 。对包含可变值的框的引用。当你在该引用上调用 MoveNext()
时,框内的值将会发生变异,所以它会做你想要的。
有关非常类似情况的分析(再次使用 List&lt; T&gt; .GetEnumerator()
),请参阅我的2010年博客文章”迭代,该死的你!“。
答案 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>.GetEnumerator
或IEnumerator<T>
的变量。使用前一种类型的代码就像List<T>.Enumerator
是引用类型一样,并且可以假设使用后者的程序员意识到它是一种结构类型。然而,当C#添加类型推断时,该假设不再成立。代码可以很容易地使用类型List<T>.Enumerator
而不需要程序员知道该类型的存在。
如果C#曾经定义了一个struct-method属性,该属性可用于标记不应在只读结构上调用的方法,并且如果List<T>.Enumerator
使用它,那么像你这样的代码可以在调用List<T>.Enumerator
时恰当地产生编译时错误,而不是产生虚假行为。但我知道没有特别的计划来添加这样的属性。