代码之家  ›  专栏  ›  技术社区  ›  ivamax9

Enumerator.MoveNext()的奇怪行为

  •  35
  • ivamax9  · 技术社区  · 9 年前

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

    var x = new { TempList = new List<int> { 1, 3, 6, 9 }.GetEnumerator() };
    while (x.TempList.MoveNext())
    {
      Console.WriteLine("Hello World");
    }
    
    2 回复  |  直到 9 年前
        1
  •  41
  •   Scott Chamberlain    9 年前

    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() 在-上 复制 匿名类型的值的。实际上,您无法更改匿名类型中的值-您所得到的只是一个可以调用的属性,它将为您提供值的副本。

    如果将代码更改为:

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

    …然后你会得到一个 参考 在匿名类型中。对包含可变值的框的引用。当你打电话时 移动下一步() 在该引用上,框内的值将发生变异,因此它将执行您所需的操作。

    用于分析非常类似的情况(再次使用 列表<T>;。获取枚举器() )参见 my 2010 blog post "Iterate, damn you!" .

        2
  •  3
  •   supercat    9 年前

    foreach 用C#和 For Each VB.NET中的循环经常与实现 IEnumerable<T> ,他们将接受包括 GetEnumerator 方法,其返回类型提供 MoveNext 功能和 Current 所有物有 方法 返回在许多情况下允许的值类型 前肢 要比返回时更有效地实施 IEnumerator<T> .

    不幸的是,当从 前肢 在由 方法 方法调用,的作者 List<T> 面临性能与语义的轻微权衡。当时,因为C#不支持变量类型推断,所以使用从 List<T>.GetEnumerator 必须声明类型为的变量 IE分子<T> List<T>.Enumerator .使用前一种类型的代码的行为如下 列表<T>;。枚举器 是一种引用类型,使用后者的程序员可以假定它是一种结构类型。然而,当C#添加了类型推断时,这种假设就不再成立了。代码很容易使用类型 列表<T>;。枚举器 而程序员不知道该类型的存在。

    如果C#曾经定义了一个struct方法属性,该属性可以用于标记不应在只读结构上调用的方法,并且如果 列表<T>;。枚举器 利用它,像您这样的代码在调用 移到下一行 而不是产生虚假行为。然而,我知道没有特别的计划添加这样的属性。

    推荐文章