Подтвердить что ты не робот

В чем причина создания IEnumerator?

IEnumerator содержит MoveNext(), Reset() и Current в качестве своих членов. Теперь предположим, что я перенес эти методы и свойства в интерфейс IEnumerable и удалил метод GetEnumerator() и IEnumerator.

Теперь объект класса, который реализует IEnumerable, сможет получить доступ к методам и свойству и, следовательно, может быть повторен.

  • Почему вышеупомянутый подход не последовал и проблемы, с которыми я столкнусь если я последую этому?
  • Каким образом наличие интерфейса IEnumerator устраняет эти проблемы?
4b9b3361

Ответ 1

Итератор содержит отдельное состояние в коллекции: он содержит курсор для того, где вы находитесь в коллекции. Таким образом, должен быть отдельный объект для представления этого дополнительного состояния, способ получить этот объект и операции над этим объектом - следовательно, IEnumeratorIEnumerator<T>), GetEnumerator() и члены итератора.

Представьте себе, если у нас не было отдельного состояния, а затем мы написали:

var list = new List<int> { 1, 2, 3 };

foreach (var x in list)
{
    foreach (var y in list)
    {
         Console.WriteLine("{0} {1}", x, y);
    }
}

Это должно печатать "1 1", "1 2", "1 3", "2 1" и т.д., но без какого-либо дополнительного состояния, как оно могло "знать" два разных положения двух петель?

Ответ 2

Теперь предположим, что я переместил эти методы и свойство в интерфейс IEnumerable и удалил метод GetEnumerator() и интерфейс IEnumerator.

Такая конструкция предотвратит параллельное перечисление в коллекции. Если бы сама коллекция отслеживала текущую позицию, вы не могли бы иметь несколько потоков, перечисляющих одну и ту же коллекцию или даже вложенные перечисления, такие как:

foreach (var x in collection)
{
    foreach (var y in collection)
    {
        Console.WriteLine("{0} {1}", x, y);
    }
}

Делегируя ответственность за отслеживание текущей позиции на другой объект (перечислитель), он делает каждое перечисление коллекции независимым от других

Ответ 3

Немного длинный ответ, два предыдущих ответа охватывают большинство из них, но я нашел некоторые аспекты, которые я нашел интересными при поиске foreach в спецификации языка C#. Если вас это не интересует, прекратите чтение.

Теперь к промежуточной части, согласно C# расширению спецификации следующих операторов:

foreach (V v in x) embedded-statement

Вы получаете:

{`
 E e = ((C)(x)).GetEnumerator();
 try {
    while (e.MoveNext()) {
        V v = (V)(T)e.Current;
        embedded-statement
    }
}
 finally {
    … // Dispose e
 }
}

Имея какую-то идентификационную функцию, которая следует за x == ((C)(x)).GetEnumerator() (это собственный счетчик), а использование циклов @JonSkeet производит что-то вроде этого (удаленный try/catch для краткости):

var list = new List<int> { 1, 2, 3 };

while (list.MoveNext()) {
 int x = list.Current; // x is always 1
 while (list.MoveNext()) {
   int y = list.Current; // y becomes 2, then 3
   Console.WriteLine("{0} {1}", x, y);
 }
}

Выведет что-то вдоль строк:

1 2
1 3

И затем list.MoveNext() вернет false навсегда. Это очень важно, если вы посмотрите на это:

var list = new List<int> { 1, 2, 3 };

// loop
foreach (var x in list) Console.WriteLine(x); // Will print 1,2,3
// loop again
// Will never enter loop, since reset wasn't called and MoveNext() returns false
foreach (var y in list) Console.WriteLine(x); // Print nothing

Итак, с учетом вышеизложенного, и обратите внимание, что это полностью выполнимо, так как оператор foreach ищет метод GetEnumerator(), прежде чем он проверяет, реализует ли тип IEnumerable<T>:

Почему вышеупомянутый подход не последовал и проблемы, с которыми я столкнусь если я последую за ним?

Вы не можете устанавливать петли и не можете использовать оператор foreach для доступа к одной и той же коллекции более одного раза без вызова Reset() вручную между ними. Также, что происходит, когда мы утилизируем счетчик после каждого foreach?

Как присутствие интерфейса IEnumerator решает эти проблемы?

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

Ответ 4

Другие уже ответили на ваш вопрос, но я хочу добавить еще небольшую деталь.

Пусть декомпилирует System.Collection.Generics.List(Of T). Его определение выглядит следующим образом:

public class List<T> : IList<T>, ICollection<T>, IList, ICollection, IReadOnlyList<T>, IReadOnlyCollection<T>, IEnumerable<T>, IEnumerable

и его определение перечислителя выглядит следующим образом:

public struct Enumerator : IEnumerator<T>, IDisposable, IEnumerator

Как вы можете видеть, Список сам по себе является классом, но его Enumerator является структурой, и этот дизайн помогает повысить производительность. Предположим, что у вас нет разделения между IEnumerable и IEnumerator. В этой ситуации вы вынуждены сделать Список структурой, но это не очень хорошая идея, поэтому вы не можете этого сделать. Таким образом, вы теряете хорошую возможность повысить производительность.

При разделении между IEnumerable и IEnumerator вы можете реализовать каждый интерфейс по своему усмотрению и использовать struct для счетчиков.

Ответ 5

Итерационная логика (foreach) не привязана к IEnumerables или IEnumerator. То, что вам нужно для работы, - это метод, называемый GetEnumerator в классе, который возвращает объект класса с методами MoveNext(), Reset() и текущим свойством. Например, следующий код работает, и он создаст бесконечный цикл.

В проектной перспективе разделение должно гарантировать, что контейнер (IEnumerable) не сохраняет какое-либо состояние во время и после завершения операций итерации (foreach).

    public class Iterator 
    {
        public bool MoveNext()
        {
            return true;
        }

        public void Reset()
        {

        }

        public object Current { get; private set; }
    }

    public class Tester
    {
        public Iterator GetEnumerator()
        {
            return new Iterator();
        }

        public static void Loop() 
        {
           Tester tester = new Tester();
           foreach (var v in tester)
           {
              Console.WriteLine(v);
           }

        }

    }