使用foreach循环时,GetEnumerator()被调用多少次

时间:2019-06-06 04:42:26

标签: c# .net oop

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

{{- if !$e.ExcludeFromIndex -}}

如果我们遍历foreach:

 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;
            }
        }
    }

输出是

 B b = new B();

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

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

我们可以看到GetEnumerator()被调用了两次,因为我们使用了两个foreach循环,每个foreach都调用一次GetEnumerator(),就足够了。

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

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

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

P.S 我知道以下事实:如果您使用yield运行代码,结果是相同的,并且GetEnumerator()似乎被调用了两次,但是由于yield是特殊的,这使得整个方法似乎对于每个foreach都只调用一次,但是该方法必须在后台多次调用(在这种情况下,GetEnumerator()将被调用10次)

1 个答案:

答案 0 :(得分:3)

非常简单地输入(并且不考虑GetEnumerator被称为相同次数的事实),yield是特例...

yield (C# Reference)

文档摘录示例的摘录

  

在foreach循环的迭代中,调用MoveNext方法   元素此调用执行MyIteratorMethod的主体   直到到达下一个收益率声明为止。表达方式   由yield return语句返回的值不仅确定值   循环体消耗的元素变量的数量,以及   元素的当前属性

编译器方法生成代码,并探查实现IEnumerator进行枚举的 (就像您所拥有的一样),as you can see here

注意 :编译器会为您生成代码并做一些您不能做的特殊事情(可能称为不一致)

给出此

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();
  }
}