Как реализовать правильный интерфейс IEnumerator для нескольких foreach? - PullRequest
4 голосов
/ 05 октября 2011

У меня есть код вроде:

        class T : IEnumerable, IEnumerator
        {
            private int position = -1;

            public T() { }

            public IEnumerator GetEnumerator() { return this; }

            public object Current { get { return position; } }

            public bool MoveNext()
            {
                position++;
                return (position < 5);
            }

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

//Using in code:
T t = new T();
foreach (int i in t)
 //to do something

В коде выше все работает нормально, но когда я использую следующее:

foreach (int i in t)
   if (i == 2)
     foreach (int p in t)
       //print p
   else
       //print i

Он печатает (в скобках второй цикл): 0 1 (3 4) 2 вместо 0 1 (0 1 2 3 4) 2 3 4 Я проверил это в списке и коллекции, и они делают это правильно. Как мне достичь того, что мне нужно?

Ответы [ 2 ]

9 голосов
/ 05 октября 2011

Вы не можете этого сделать, потому что вы сделали свой код единым перечислителем, что само по себе является ошибкой IMO.Лучшая версия была бы для меня:

class T : IEnumerable<int> {
    public IEnumerator<int> GetEnumerator() {
        int i = 0;
        while(i < 5) {
            yield return i;
            i++;
        }
    }
    IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); }
}

Компилятор создаст правильные устройства для достижения этого с отдельными перечислителями.

Если вы не пишете для .NET 1.1, то если вынайди себя вручную при написании энумаратора, есть очень хороший шанс, что ты сделаешь это нелегко и ошибочно получишь бонус.

Если ты действительно должен сделать это нелегко:

class T : IEnumerable<int>
{
    public T() { }

    public IEnumerator<int> GetEnumerator() { return new TEnumerator(); }
    IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); }
    private class TEnumerator : IEnumerator<int>
    {
        private int position = -1;
        public int Current { get { return position; } }
        object IEnumerator.Current { get { return Current; } }
        void IDisposable.Dispose() {}
        public bool MoveNext()
        {
            position++;
            return (position < 5);
        }
        public void Reset() { position = -1; }
    } 
}

Здесь важно, что различные экземпляры TEnumerator позволяют один и тот же экземпляр T повторяться отдельно.

5 голосов
/ 05 октября 2011
foreach (int i in t)
   if (i == 2)
     foreach (int p in t)
       //print p
   else
       //print i

Сначала всегда используйте фигурные скобки , в то время как вы делаете отступ, совпадающий с тем, что произойдет с другой if, что может привести к путанице.

foreach (int i in t) {
   if (i == 2) {
     foreach (int p in t) {
       //print p
      }
   } else {
       //print i
   }
 }

Но у вас проблема: у вас есть только один счетчик на экземпляр T, и вы используете тот же экземпляр. Поэтому вы проходите один раз. Если вы хотите разрешить одновременные перечисления, объект перечислителя должен быть отделен от GetEnumerator, каждый раз возвращая новый экземпляр.

...