首页 > 解决方案 > 使用 foreach 循环时调用 GetEnumerator() 多少次

问题描述

以下是我制作的自定义类:

 class B : IEnumerable
    {
        int[] data = { 0, 1, 2, 3, 4 };
        public IEnumerator GetEnumerator()
        {
            Console.WriteLine("Called");
            return new BEnumerator(this);
        }

        private class BEnumerator : IEnumerator
        {
            private B instance;
            private int position = -1;

            public BEnumerator(B inst)
            {
                this.instance = inst;
            }

            public object Current
            {
                get
                {
                    return instance.data[position];
                }
            }

            public bool MoveNext()
            {
                position++;
                return (position < instance.data.Length);
            }

            public void Reset()
            {
                position = -1;
            }
        }
    }

如果我们遍历foreach:

 B b = new B();

 foreach (var item in b)
 {
    Console.WriteLine(item);
 }

 foreach (var item in b)
 {
    Console.WriteLine(item);
 }

输出是

called
0
1
2
3
4
called
0
1
2
3
4

我们可以看到 GetEnumerator() 被调用了两次,因为我们使用了两个 foreach 循环,每个 foreach 调用 GetEnumerator() 一次,这很公平。

但是如果我们将迭代器修改为

 public IEnumerator GetEnumerator()
 {
     yield return data[0];
     yield return data[1];
     yield return data[2];
     yield return data[3];
     yield return data[4];
 }

很容易看出,GetEnumerator() 将被调用五次以获取每个值。那么为什么 GetEnumerator() 只调用一次,有时调用多次,这是不一致的呢?

PS我知道如果你用yield运行代码,结果是一样的,GetEnumerator()似乎被调用了两次,但是因为yield是特殊的,这使得整个方法似乎每次foreach只调用一次,但是该方法必须在后台多次调用(在这种情况下 GetEnumerator() 将被调用 10 次)

标签: c#.netoop

解决方案


简单地说(并且忽略GetEnumerator被称为相同次数的事实),yield是一种特殊情况......

产量(C# 参考)

摘自文档中给出的示例

在 foreach 循环的迭代中,为元素调用 MoveNext 方法此调用执行 MyIteratorMethod 的主体,直到到达下一个 yield return 语句。yield return语句返回的表达式不仅决定了循环体消耗的元素变量的值,还决定了元素的Current属性

编译器为该方法生成代码查找一个实现枚举(就像您拥有的那样),如您在此处看到的IEnumerator

注意:编译器为您生成代码并执行您无法执行的特殊操作(您可能称之为不一致)的地方很多

鉴于这种

public IEnumerator GetEnumerator()
{
    yield return data[0];
    yield return data[1];
    yield return data[2];
    yield return data[3];
    yield return data[4];
}

编译器像这样生成你的方法

[IteratorStateMachine(typeof(<GetEnumerator>d__1))]
public IEnumerator GetEnumerator()
{
    <GetEnumerator>d__1 <GetEnumerator>d__ = new <GetEnumerator>d__1(0);
    <GetEnumerator>d__.<>4__this = this;
    return <GetEnumerator>d__;
}

并生成这样的类

[CompilerGenerated]
private sealed class <GetEnumerator>d__1 : IEnumerator<object>, IDisposable, IEnumerator
{
  private int <>1__state;

  private object <>2__current;

  public C <>4__this;

  object IEnumerator<object>.Current
  {
      [DebuggerHidden]
      get
      {
          return <>2__current;
      }
  }

  object IEnumerator.Current
  {
      [DebuggerHidden]
      get
      {
          return <>2__current;
      }
  }

  [DebuggerHidden]
  public <GetEnumerator>d__1(int <>1__state)
  {
      this.<>1__state = <>1__state;
  }

  [DebuggerHidden]
  void IDisposable.Dispose()
  {
  }

  private bool MoveNext()
  {
      switch (<>1__state)
      {
          default:
              return false;
          case 0:
              <>1__state = -1;
              <>2__current = <>4__this.data[0];
              <>1__state = 1;
              return true;
          case 1:
              <>1__state = -1;
              <>2__current = <>4__this.data[1];
              <>1__state = 2;
              return true;
          case 2:
              <>1__state = -1;
              <>2__current = <>4__this.data[2];
              <>1__state = 3;
              return true;
          case 3:
              <>1__state = -1;
              <>2__current = <>4__this.data[3];
              <>1__state = 4;
              return true;
          case 4:
              <>1__state = -1;
              <>2__current = <>4__this.data[4];
              <>1__state = 5;
              return true;
          case 5:
              <>1__state = -1;
              return false;
      }
  }

  bool IEnumerator.MoveNext()
  {
      //ILSpy generated this explicit interface implementation from .override directive in MoveNext
      return this.MoveNext();
  }

  [DebuggerHidden]
  void IEnumerator.Reset()
  {
      throw new NotSupportedException();
  }
}

推荐阅读