Зачем нам нужны два интерфейса для перечисления коллекции?

Я довольно давно пытался понять идею IEnumerable и IEnumerator. Я прочитал все вопросы и ответы, которые смог найти в сети, и в частности на StackOverflow, но меня это не удовлетворило. Я дошел до того, что понимаю как эти интерфейсы, но не понимаю, почему они используются таким образом.

Думаю, что суть моего недоразумения в том, что нам нужно два интерфейса для одной операции. Я понял, что если нужны оба, то одного, вероятно, недостаточно. Поэтому я взял "жестко закодированный" эквивалент foreach (как я нашел здесь):

while (enumerator.MoveNext())
{
    object item = enumerator.Current;

    // logic
}

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

Итак, я создал класс коллекции и реализовал IForeachable:

class Collection : IForeachable
{
    private int[] array = { 1, 2, 3, 4, 5 };
    private int index = -1;

    public int Current => array[index];

    public bool MoveNext()
    {
        if (index < array.Length - 1)
        {
            index++;
            return true;
        }

        index = -1;
        return false;
    }
}

и использовал эквивалент foreach для номинации коллекции:

var collection = new Collection();

while (collection.MoveNext())
{
    object item = collection.Current;

    Console.WriteLine(item);
}

И это работает! Так чего же здесь не хватает, чтобы потребовать другого интерфейса?

Спасибо.


Изменить: Мой вопрос не дублирует вопросы, перечисленные в комментариях:

  • Этот вопрос заключается в том, почему интерфейсы необходимы для перечисления в первое место.
  • Этот вопрос и этот вопрос касается что это интерфейсы и как < / em> следует ли их использовать.

У меня вопрос: почему они спроектированы именно так, а не что из них, как они работают и зачем они нам нужны в первое место.


person Sipo    schedule 31.03.2017    source источник
comment
Итак, как вы могли бы просматривать свою коллекцию одновременно из двух разных потоков?   -  person Sergej Christoforov    schedule 31.03.2017
comment
Представьте, что вы хотите перебрать файл; вы должны открыть, перебрать и закрыть его; кажется разумным скрыть эти операции в IEnumerator<T>   -  person Dmitry Bychenko    schedule 31.03.2017
comment
сделайте паузу в while для некоторого условия и выполните еще один цикл снова, и вы будете просветлены.   -  person Mat J    schedule 31.03.2017


Ответы (2)


Что это за два интерфейса и для чего они нужны?

Интерфейс IEnumerable помещается в объект коллекции и определяет метод GetEnumerator (), который возвращает (обычно новый) объект, который реализует интерфейс IEnumerator. Оператор foreach в C # и оператор For Each в VB.NET используют IEnumerable для доступа к перечислителю, чтобы перебирать элементы в коллекции.

Интерфейс IEnumerator - это, по сути, контракт, размещенный на объекте, который фактически выполняет итерацию. Он сохраняет состояние итерации и обновляет его по мере продвижения кода по коллекции.

Почему бы просто не сделать коллекцию перечислителем? Зачем нужны два разных интерфейса?

Нет ничего, что могло бы помешать реализации IEnumerator и IEnumerable в одном классе. Однако за это есть штраф - невозможно иметь два или более петель в одной и той же коллекции одновременно. Если можно абсолютно гарантировать, что никогда не будет необходимости повторять цикл для коллекции дважды за одно и то же время, тогда это нормально. Но в большинстве случаев это невозможно.

Когда кто-то будет перебирать коллекцию более одного раза за раз?

Вот два примера.

Первый пример - это когда в одной коллекции есть два цикла, вложенных друг в друга. Если бы коллекция была также перечислителем, тогда было бы невозможно поддерживать вложенные циклы в одной и той же коллекции, когда код попадет во внутренний цикл, он столкнется с внешним циклом.

Второй пример - это когда два или более потоков обращаются к одной и той же коллекции. Опять же, если бы коллекция была также перечислителем, то было бы невозможно поддерживать безопасную многопоточную итерацию для той же коллекции. Когда второй поток пытается перебрать элементы в коллекции, состояние двух перечислений будет конфликтовать.

Кроме того, поскольку итерационная модель, используемая в .NET, не позволяет вносить изменения в коллекцию во время перечисления, в остальном эти операции полностью безопасны.

- Это из сообщения в блоге, которое я написал много лет назад: https://colinmackay.scot/2007/06/24/iteration-in-net-with-ienumerable-and-ienumerator/

person Colin Mackay    schedule 31.03.2017
comment
Еще один момент для нескольких IEnumerator заключается в том, что они могут реализовывать различное поведение, например обратный перечислитель, который начинается с последнего элемента и идет в обратном направлении. - person Christian Strempfer; 31.03.2017

Ваш IForeachable даже не может быть повторен из двух разных потоков (у вас вообще не может быть нескольких активных итераций - даже из одного потока), потому что текущее состояние перечисления хранится в самом IForeachable. Вам также необходимо сбрасывать текущую позицию каждый раз, когда вы заканчиваете перечисление, и если вы забыли это сделать - что ж, следующий вызывающий будет думать, что ваша коллекция пуста. Я могу только представить себе, к каким ошибкам это все может привести.

С другой стороны, поскольку IEnumerable возвращает новый IEnumerator для каждого вызывающего абонента, вы можете иметь несколько выполняемых перечислений одновременно, потому что каждый вызывающий объект имеет собственное состояние перечисления. Я думаю, что одной этой причины достаточно, чтобы оправдать наличие двух интерфейсов. Перечисление - это, по сути, операция чтения, и было бы очень запутанно, если бы вы не могли прочитать одно и то же одновременно в нескольких местах.

person Evk    schedule 31.03.2017
comment
На мой взгляд, проблема с потоками здесь - отвлекающий маневр. Важная проблема заключается в том, что вы не сможете иметь две активные итерации одновременно, точка. Они вполне могли быть из одной ветки. +1 - person InBetween; 31.03.2017
comment
@InBetween Я согласен с этим (обновленный ответ), однако, я думаю, проблему с потоками проще всего понять. - person Evk; 31.03.2017
comment
Привет! Спасибо за Ваш ответ. Я НИЧЕГО не знаю о потоках, поэтому я выбрал ответ @ColinMackay из-за уровня детализации и примера с вложенными циклами. Еще раз спасибо! - person Sipo; 31.03.2017