Как реализовать правильный интерфейс IEnumerator для множественного foreach?

#c# #.net #ienumerable #ienumerator

#c# #.net #ienumerable #ienumerator

Вопрос:

У меня есть код, подобный:

         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
  

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

 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
Я протестировал его в списке и коллекции, и они все делают правильно.
Как я могу достичь того, что мне нужно?

Комментарии:

1. Просто в качестве примечания: еще один способ показать, что текущая реализация неверна: Console.WriteLine(t.Count()); // 5 Console.WriteLine(t.Count()); // 0 т. Е. В настоящее время она будет повторяться только один раз (обратите внимание также, что Reset() никогда не следует использовать — он формально устарел и в большинстве случаев намеренно генерирует исключения)

Ответ №1:

Вы не можете, потому что вы сделали свой код с одним перечислителем, что само по себе является ошибкой 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, то, если вы обнаружите, что вручную пишете enumarator, есть очень хороший шанс, что вы делаете это сложным путем и получаете его неправильно в качестве бонуса.

Если вы действительно должны сделать это сложным способом:

 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 экземпляру повторяться отдельно.

Комментарии:

1. @Praetor12 да, но вам понадобится второй тип для представления перечислителя. Я смоделирую это … 2 секунды.

2. Кажется, это немного сложнее, чем я ожидал, но это то, что мне нужно. Большое спасибо!

3. @Praetor12 из болезненного любопытства, зачем вам нужно писать перечислитель сложным способом?

4. Мне нравится писать код, который работает так, как я понимаю. Я не знаю, как именно работает yeild: ( Вот и все.

5. @Praetor12 вам может понравиться серия постов в блоге Эрика Липперта о блоках итераторов и, в частности, статьи в блогах Рэймонда Чена и Джона Скита , ссылки на которые приведены в части 2

Ответ №2:

 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 , и вы используете один и тот же экземпляр. Поэтому вы делаете через один раз. Если вы хотите разрешить одновременные перечисления, объект enumerator должен быть отдельным GetEnumerator , каждый раз возвращая новый экземпляр.