Розрізнення між ітератором та перелічувачем


78

Запитання для співбесіди для завдання .NET 3.5: "Яка різниця між ітератором та перелічувачем"?

Це основна відмінність, яку слід зробити, що з LINQ тощо.

У всякому разі, яка різниця? Здається, я не можу знайти твердого визначення в мережі. Не помиліться, я можу знайти значення двох термінів, але я отримую дещо різні відповіді. Якою буде найкраща відповідь на співбесіду?

IMO ітератор "перебирає" над колекцією, а перерахувач забезпечує функціональність для ітерації, але це потрібно викликати.

Крім того, використовується ключове слово yield, щоб зберегти стан. Що саме це за стан? Чи є приклад такої вигоди?

Відповіді:


57

Ітерація означає повторення деяких кроків, тоді як перерахування означає перегляд усіх значень у колекції значень. Отже, перерахування зазвичай вимагає певної форми ітерації.

Таким чином, перелічення є особливим випадком ітерації, коли крок отримує значення з колекції.

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

Ви також можете перерахувати значення, які явно не зберігаєте в колекції. Наприклад, ви можете перерахувати натуральне число, прості числа або що завгодно, але ви обчислили б ці значення під час перерахування, а не отримували їх із фізичної колекції. Ви розумієте цей випадок як перерахування віртуальної колекції з її значеннями, визначеними деякою логікою.


Я припускаю, Рід Копсі зрозумів справу. У C # є два основних способи щось перерахувати.

  1. Реалізація Enumerableта реалізація класуIEnumerator
  2. Реалізуйте ітератор із yieldтвердженням

Перший спосіб важче реалізувати і використовує об’єкти для перерахування. Другий спосіб простіший у реалізації та використовує продовження.


6
Однак у C # ітератор - це специфічна, спеціальна конструкція, а також просто дієслово, що описує термін. Також перелічувач - це специфічний інтерфейс. Ці два мають абсолютно різне значення у C # проти звичайних термінів ОО.
Рід Копсі,

2
О так, ти маєш рацію. У моїх термінах це все перелічення, і я повністю забув, що C # викликає цей ітератор шаблону.
Даніель Брюкнер,

У C # ітератор - це метод, що повертає перечислювач (магія компілятора, коли він бачить yield), а перечислювач - це об’єкт, що передає значення по одному за допомогою Currentі MoveNext()(див . Відповідь Ping ). Формулювання різниться в Python, де генератор схожий на перелічувач C # (значення, створені на льоту з використанням yield), а старий ітератор робить те ж саме, але без yield(тому жодного механізму паузи цикл використовувати не можна).
хв.

46

У C # 2+ ітератори - це спосіб для компілятора автоматично генерувати для вас інтерфейси IEnumerable та / або IEnumerable <T>.

Без ітераторів вам потрібно буде створити клас, що реалізує IEnumerator , включаючи Current, MoveNext та Reset. Це вимагає неабиякої роботи. Зазвичай ви створюєте приватний клас, який реалізує IEnumerator <T> для вашого типу, тоді yourClass.GetEnumerator () створює цей приватний клас і повертає його.

Ітератори - це спосіб для компілятора автоматично генерувати це для вас, використовуючи простий синтаксис (yield). Це дозволяє вам реалізувати GetEnumerator () безпосередньо у своєму класі, не вказавши другий клас (The IEnumerator). Побудова цього класу з усіма його членами зроблена для вас.

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

Коли ви використовуєте foreach, вони поводитимуться однаково (за умови, що ви правильно пишете власний IEnumerator). Ітератори просто роблять життя набагато простішим.


22

Те, що C # називає ітератором , частіше (за межами світу C #) називається генератором або функцією генератора (наприклад, на Python). Функція генератора - це спеціалізований випадок корутину . Ітератор (генератор) AC # - це спеціальна форма перечислювача (тип даних, що реалізує IEnumerableінтерфейс).

Мені не подобається таке вживання терміна ітератор для генератора C #, оскільки це не менше як перелічувач, скільки ітератор. Однак для Microsoft занадто пізно передумав.

На відміну від цього, враховуйте, що в C ++ ітератор - це значення, яке використовується переважно для доступу до послідовних елементів у колекції. Його можна просунути, вивести на розмиття для отримання значення та протестувати, щоб побачити, чи досягнуто кінця колекції.


2
Ну, це найкраща відповідь тут;)
mrzepa

13

"Тоді як заява foreach є споживачем перелічувача, ітератор є виробником перерахувача".

Вище викладене, як це пояснює "C # 5.0 in NutShell", і було для мене корисним.

Іншими словами, оператор foreach використовує MoveNext () та властивість Current IEnumerator для ітерації через послідовність, тоді як ітератор використовується для створення реалізації IEnumerator, який буде використовуватися оператором foreach. У C #, коли ви пишете метод ітератора, що містить оператор yield, компілятор створить для вас приватний перечислювач. І коли ви переглядаєте елементи у послідовності, він викликає властивість MoveNext () і Current приватного перечислювача. Ці методи / властивості реалізовані вашим кодом в ітераторному методі, який буде викликатися повторно, щоб отримати значення, поки не залишиться значень для отримання.

Це моє розуміння того, як C # визначає перечислювачі та ітератори.


1
Це речення узагальнює це дуже стисло. Шкода, що ця відповідь поки що недоступна, майте мій +1!
AnorZaken

12

Щоб зрозуміти ітератори, нам спочатку потрібно зрозуміти перелічувачі.

Перелічувачі - це спеціалізовані об'єкти, які забезпечують один засіб пересування по впорядкованому списку елементів по одному (той самий предмет іноді називають "курсором"). Рамка .NET надає два важливі інтерфейси, що стосуються перечислювачів: IEnumerator та IEnumerable. Об'єкти, що реалізують IEnumerator, самі є перечислювачами; вони підтримують таких членів:

  • властивість Current, яка вказує на позицію у списку

  • метод MoveNext, який переміщує поточний елемент по списку

  • метод Скидання, який переміщує Поточний елемент у вихідне положення (який знаходиться перед першим елементом).

З іншого боку, ітератори реалізують схему нумерації. .NET 2.0 представив ітератор, що є маніфестованим компілятором перечислювачем. Коли обчислюваний об'єкт викликає GetEnumerаtor, прямо чи опосередковано, компілятор генерує та повертає відповідний об'єкт ітератора. Як варіант, ітератор може бути комбінованим перечислюваним та обчислювачем об’єктом.

Основним інгредієнтом ітераторного блоку є інформація про вихід. Існує одна велика різниця між ітераторами та нумераторами: ітератори не реалізують метод Reset. Виклик методу скидання на ітераторі викликає виняток.

Сенс ітераторів полягає в тому, щоб забезпечити легку реалізацію перечислювачів. Якщо для впорядкованого списку елементів методу потрібно повернути перечислювач або перелічуваний клас, він записується таким чином, щоб повернути кожен елемент у правильному порядку, використовуючи оператор yield.


8

Оскільки прикладів не наводилось, ось один, який був для мене корисним.

Перечислювач - це об'єкт, який ви отримуєте при виклику .GetEnumerator () класу або типу, що реалізує інтерфейс IEnumerator. Коли цей інтерфейс реалізований, ви створили весь код, необхідний компілятору для використанняforeach "ітерацію" над вашою колекцією.

Нехай це слово "ітерація" не плутається з ітератором. І перелік, і ітератор дозволяють вам "ітерацію". Перерахування та ітерація - це в основному один і той же процес, але реалізується по-різному. Перерахування означає, що ви застосували інтерфейс IEnumerator Ітерація означає, що ви створили конструкцію ітератора у своєму класі (продемонстровано нижче), і ви телефонуєте foreach свій клас, тоді компілятор автоматично створює для вас функціональність перечислювача.

Також зауважте, що вам не потрібно робити присідання зі своїм перелічувачем. Ви можете телефонувати MyClass.GetEnumerator()цілий день і нічого з цим не робити (приклад:

IEnumerator myEnumeratorThatIWillDoNothingWith = MyClass.GetEnumerator()).

Також зауважте, що ваша конструкція ітератора у вашому класі застосовується лише тоді, коли ви насправді використовуєте її, тобто ви телефонували foreach до свого класу.

Ось приклад ітератора з msdn :

public class DaysOfTheWeek : System.Collections.IEnumerable
{

     string[] days = { "Sun", "Mon", "Tue", "Wed", "Thr", "Fri", "Sat" };

     //This is the iterator!!!
     public System.Collections.IEnumerator GetEnumerator()
     {
         for (int i = 0; i < days.Length; i++)
         {
             yield return days[i];
         }
     }

}

class TestDaysOfTheWeek
{
    static void Main()
    {
        // Create an instance of the collection class
        DaysOfTheWeek week = new DaysOfTheWeek();

        // Iterate with foreach - this is using the iterator!!! When the compiler
        //detects your iterator, it will automatically generate the Current, 
        //MoveNext and Dispose methods of the IEnumerator or IEnumerator<T> interface
        foreach (string day in week)
        {
            System.Console.Write(day + " ");
        }
    }
}
// Output: Sun Mon Tue Wed Thr Fri Sat

4

"Ітератори - це нова функція в C # 2.0. Ітератор - це метод, отримати доступ або оператор, який дозволяє підтримувати кожну ітерацію в класі або структурі без необхідності реалізовувати весь інтерфейс IEnumerable. Натомість ви надаєте лише ітератор, який просто обходить структури даних у вашому класі. Коли компілятор виявить ваш ітератор, він автоматично генерує методи Current, MoveNext та Dispose інтерфейсу IEnumerable або IEnumerable. " - msdn


2

Перерахування має справу з об'єктами, тоді як ітерація має справу лише зі значеннями. Перерахування використовується, коли ми використовуємо векторну хеш-таблицю тощо, тоді як ітерація використовується в циклі while для циклу тощо. Я ніколи не використовував ключове слово yield, тому я не міг вам сказати.


Це може бути правдою, але лише для крапкової мережі. У деяких мовах ви можете переглядати об'єкти / значення та перераховувати об'єкти / значення
Тім Метьюз

Ну, я програміст .NET. Вибачте за своє незнання, саме так я дізнався, як вони працювали.
Креднс,

0

Ітерація має справу з масивами та рядками, а ітерація - з об’єктами

У JavaScript ви можете повторити масив або рядок за допомогою:

  • forEach Loop
  • для циклу
  • для Петлі
  • робити, поки петля
  • поки Петля

І ви можете перерахувати об’єкт за допомогою:

  • для в Петлі
  • Object.keys () Метод
  • Object.values () Метод
  • Метод Object.entries ()
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.