Зачем нам нужны итераторы в c #? - PullRequest
11 голосов
/ 04 августа 2009

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

Ответы [ 10 ]

16 голосов
/ 04 августа 2009

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

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

В .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;
}

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

12 голосов
/ 04 августа 2009

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

6 голосов
/ 04 августа 2009

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

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;
        }
    }
}

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

4 голосов
/ 04 августа 2009

Итерация по ученикам в классе

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

3 голосов
/ 04 августа 2009

Выполните итерацию по набору домашних заданий.

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

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

2 голосов
/ 04 августа 2009

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

// 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;
    }      
}

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

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


  1. Я не употребляю это слово легкомысленно - итерация O (n) может стать O (N ^ 2)
2 голосов
/ 04 августа 2009

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

2 голосов
/ 04 августа 2009

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

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

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

1 голос
/ 04 августа 2009
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");
}
1 голос
/ 04 августа 2009

Итератор - это простой способ реализации интерфейса 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
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...