Зачем нужны итераторы в C#?

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

10 ответов


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


вы, вероятно, слышали о массивах и контейнерах-объектах, которые хранят список других объектов.

но для того, чтобы объект представлял список, он на самом деле не должен "хранить" список. Все, что ему нужно сделать, это предоставить вам методы или свойства, которые позволяют получить элементы списка.

в .NET framework интерфейс IEnumerable это все, что объект должен поддерживать, чтобы считаться "списком" в этом чувство.

чтобы немного упростить его (оставив некоторый исторический багаж):

public interface IEnumerable<T>
{
    IEnumerator<T> GetEnumerator();
}

таким образом, вы можете получить перечислитель из него. Этот интерфейс (опять же, немного упрощая, чтобы удалить отвлекающий шум):

public interface IEnumerator<T>
{
    bool MoveNext();
    T Current { get; }
}

Итак, чтобы прокрутить список, вы сделаете это:

var e = list.GetEnumerator();
while (e.MoveNext())
{
    var item = e.Current;

    // blah
}

эта картина захвачена аккуратно foreach ключевые слова:

foreach (var item in list)
    // blah

но как насчет создания нового типа списка? Да, мы можем просто использовать List<T> и заполнить его с вещами. Но что, если мы хотим обнаружить элементы "на лету", как они просят? В этом есть преимущество, которое заключается в том, что клиент может отказаться от итерации после первых трех элементов, и им не нужно "платить стоимость" создания всего списка.

реализовать такой ленивый список вручную было бы хлопотно. Нам нужно будет написать два класса, один из которых будет представлять список, реализуя IEnumerable<T>, а другой для представления активного перечисления операции по реализации IEnumerator<T>.

методы итератора делают всю тяжелую работу за нас. Мы просто пишем:

IEnumerable<int> GetNumbers(int stop)
{
    for (int n = 0; n < stop; n++)
        yield return n;
}

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


простой пример : функция, которая генерирует последовательность целых чисел :

static IEnumerable<int> GetSequence(int fromValue, int toValue)
{
    if (toValue >= fromValue)
    {
        for (int i = fromValue; i <= toValue; i++)
        {
            yield return i;
        }
    }
    else
    {
        for (int i = fromValue; i >= toValue; i--)
        {
            yield return i;
        }
    }
}

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


итерация через студентов в классе

шаблон проектирования итератора обеспечивает us с общим методом перечисления список значений или массив, скрываясь детали списка реализация. Это обеспечивает более чистое использование объекта array и скрывает ненужные информации клиент, в конечном счете, приводит к лучший код-повторное использование, расширение ремонтопригодность и меньше ошибок. Этот шаблон итератора может перечислять список элементов независимо от их фактический тип хранения.


повторите набор вопросов домашней работы.

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

прочитайте первые два абзаца здесь немного подробнее.


пара вещей, для которых они отлично подходят:
а) для "воспринимаемой производительности" при сохранении аккуратности кода - итерация чего-то, отделенного от другой логики обработки.
b) когда количество элементов, которые вы собираетесь перебирать, неизвестно.

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

пример реальной жизни: перечисление каталогов и файлов и поиск первых [n], которые удовлетворяют некоторым критериям, например файл, содержащий определенную строку или последовательность и т. д...


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


канонический и самый простой пример заключается в том, что он делает бесконечные последовательности возможными без сложности написания класса, чтобы сделать это самостоятельно:

// generate every prime number
public IEnumerator<int> GetPrimeEnumerator()
{
    yield return 2;
    var primes = new List<int>();
    primesSoFar.Add(2);
    Func<int, bool> IsPrime = n => primes.TakeWhile(
        p => p <= (int)Math.Sqrt(n)).FirstOrDefault(p => n % p == 0) == 0;

    for (int i = 3; true; i += 2)
    {
        if (IsPrime(i))
        {
            yield return i;
            primes.Add(i);
        }
    }
}

очевидно, что это не будет действительно бесконечным, если вы не используете BigInt вместо int, но это дает вам идею. Написание этого кода (или аналогичного) для каждой сгенерированной последовательности было бы утомительным и подверженным ошибкам. итераторы делают это за вас. Если приведенный выше пример кажется вам слишком сложным подумайте:

// generate every power of a number from start^0 to start^n
public IEnumerator<int> GetPowersEnumerator(int start)
{   
    yield return 1; // anything ^0 is 1
    var x = start;
    while(true)
    {
        yield return x;
        x *= start;
    }      
}

Они приходят по цене, хотя. Их ленивое поведение означает, что вы не можете обнаружить общие ошибки (нулевые параметры и тому подобное), пока генератор не будет первым потреблено вместо того, чтобы создавать без записи функций обертывания, чтобы проверить сначала. Текущая реализация также невероятно bad (1) при рекурсивном использовании.

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


  1. я не использую это слово легко - A O (n) итерация может стать O (N^2)

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

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

public IEnumerable<int> GetDouble() {
   foreach (int n in originalList) yield return n * 2;
}

В C# 3 Вы можете сделать что-то похожее, используя методы расширения и лямбда-выражения:

originalList.Select(n => n * 2)

или с помощью LINQ:

from n in originalList select n * 2

IEnumerator<Question> myIterator = listOfStackOverFlowQuestions.GetEnumerator();
while (myIterator.MoveNext())
{
  Question q;
  q = myIterator.Current;
  if (q.Pertinent == true)
     PublishQuestion(q);
  else
     SendMessage(q.Author.EmailAddress, "Your question has been rejected");
}


foreach (Question q in listOfStackOverFlowQuestions)
{
    if (q.Pertinent == true)
        PublishQuestion(q);
    else    
        SendMessage(q.Author.EmailAddress, "Your question has been rejected");
}