使用foreach循环时,GetEnumerator()方法被调用了多少次?

31 浏览
0 Comments

使用foreach循环时,GetEnumerator()方法被调用了多少次?

下面是我自己创建的自定义类:\n

 class B : IEnumerable
    {
        int[] data = { 0, 1, 2, 3, 4 };
        public IEnumerator GetEnumerator()
        {
            Console.WriteLine("被调用");
            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;
            }
        }
    }

\n如果我们通过foreach进行迭代:\n

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

\n输出结果为:\n

被调用
0
1
2
3
4
被调用
0
1
2
3
4

\n我们可以看到,由于使用了两个foreach循环,GetEnumerator()方法被调用了两次,每个foreach循环调用一次,这是可以理解的。\n但是,如果我们将迭代器修改为:\n

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

\n很容易看出,为了获取每个值,GetEnumerator()方法将被调用五次。那么,为什么GetEnumerator()有时只被调用一次,有时被调用多次,这是不一致的呢?\nP.S\n我知道使用yield运行代码时,结果是相同的,并且似乎GetEnumerator()只被调用了两次,但是由于yield的特殊性,使得整个方法对于每个foreach循环似乎只被调用一次,但是在后台实际上是需要多次调用的(在这种情况下GetEnumerator()将被调用10次)。

0
0 Comments

在使用foreach循环时,GetEnumerator()方法被调用了多少次的问题,其原因是yield语句的特殊性。根据微软官方文档中的示例,可以看到在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(d__1))]
public IEnumerator GetEnumerator()
{
    d__1 d__ = new d__1(0);
    d__.<>4__this = this;
    return d__;
}

同时会生成一个类:

[CompilerGenerated]
private sealed class d__1 : IEnumerator, IDisposable, IEnumerator
{
  private int <>1__state;
  private object <>2__current;
  public C <>4__this;
  object IEnumerator.Current
  {
      [DebuggerHidden]
      get
      {
          return <>2__current;
      }
  }
  object IEnumerator.Current
  {
      [DebuggerHidden]
      get
      {
          return <>2__current;
      }
  }
  [DebuggerHidden]
  public 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()
  {
      return this.MoveNext();
  }
  [DebuggerHidden]
  void IEnumerator.Reset()
  {
      throw new NotSupportedException();
  }
}

需要注意的是,类d__1相当于你的BEnumerator类,GetEnumerator()方法相当于返回BEnumerator的方法,而该方法只会被调用一次。唯一使代码变得复杂的是其中的元素名称,但它们只是名称,没有实际的区别。

总结起来,使用yield关键字时,GetEnumerator()方法只会被调用一次。

0