Может кто-нибудь объяснить мне IEnumerable и IEnumerator? - PullRequest
237 голосов
/ 17 февраля 2009

Может кто-нибудь объяснить мне IEnumerable и IEnumerator?

например, когда использовать его поверх foreach? В чем разница между IEnumerable и IEnumerator? Почему мы должны использовать это?

Ответы [ 16 ]

239 голосов
/ 17 февраля 2009

например, когда использовать его поверх foreach?

Вы не используете IEnumerable "над" foreach. Реализация IEnumerable делает возможным использование foreach .

Когда вы пишете код вроде:

foreach (Foo bar in baz)
{
   ...
}

функционально эквивалентно написанию:

IEnumerator bat = baz.GetEnumerator();
while (bat.MoveNext())
{
   bar = (Foo)bat.Current
   ...
}

Под «функционально эквивалентным» я подразумеваю, что фактически это то, во что компилятор превращает код. Вы не можете использовать foreach на baz в этом примере , если baz не реализует IEnumerable.

IEnumerable означает, что baz реализует метод

IEnumerator GetEnumerator()

Объект IEnumerator, который возвращает этот метод, должен реализовывать методы

bool MoveNext()

и

Object Current()

Первый метод переходит к следующему объекту в IEnumerable объекте, который создал перечислитель, возвращая false, если это сделано, а второй возвращает текущий объект.

Все, что вы можете перебрать в .Net, реализует IEnumerable. Если вы создаете свой собственный класс, и он еще не наследуется от класса, который реализует IEnumerable, вы можете сделать свой класс пригодным для использования в операторах foreach, реализовав IEnumerable (и создав класс перечислителя, который его новый метод GetEnumerator вернется).

135 голосов
/ 31 августа 2014

Интерфейсы IEnumerable и IEnumerator

Чтобы начать изучение процесса реализации существующих интерфейсов .NET, давайте сначала рассмотрим роль IEnumerable и IEnumerator. Напомним, что C # поддерживает ключевое слово с именем foreach, которое позволяет вам перебирать содержимое любого типа массива:

// Iterate over an array of items.
int[] myArrayOfInts = {10, 20, 30, 40};
foreach(int i in myArrayOfInts)
{
   Console.WriteLine(i);
}

Хотя может показаться, что эту конструкцию могут использовать только типы массивов, правда в том, что Любой тип, поддерживающий метод с именем GetEnumerator (), может быть оценен конструкцией foreach. иллюстрируй, следуй за мной!

Предположим, у нас есть класс Garage:

// Garage contains a set of Car objects.
public class Garage
{
   private Car[] carArray = new Car[4];
   // Fill with some Car objects upon startup.
   public Garage()
   {
      carArray[0] = new Car("Rusty", 30);
      carArray[1] = new Car("Clunker", 55);
      carArray[2] = new Car("Zippy", 30);
      carArray[3] = new Car("Fred", 30);
   }
}

В идеале было бы удобно перебирать подэлементы объекта Garage, используя foreach построить, как массив значений данных:

// This seems reasonable ...
public class Program
{
   static void Main(string[] args)
   {
      Console.WriteLine("***** Fun with IEnumerable / IEnumerator *****\n");
      Garage carLot = new Garage();
      // Hand over each car in the collection?
      foreach (Car c in carLot)
      {
         Console.WriteLine("{0} is going {1} MPH",
         c.PetName, c.CurrentSpeed);
      }
      Console.ReadLine();
   }
}

К сожалению, компилятор сообщает вам, что класс Garage не реализует метод с именем GetEnumerator (). Этот метод формализован интерфейсом IEnumerable, который находится в пространстве имен System.Collections. Классы или структуры, которые поддерживают это поведение, объявляют, что они могут раскрыть содержимое подпункты к вызывающей стороне (в этом примере, само ключевое слово foreach). Вот определение этого стандартного интерфейса .NET:

// This interface informs the caller
// that the object's subitems can be enumerated.
public interface IEnumerable
{
   IEnumerator GetEnumerator();
}

Как видите, метод GetEnumerator () возвращает ссылку на еще один интерфейс с именем System.Collections.IEnumerator. Этот интерфейс обеспечивает инфраструктуру, позволяющую вызывающей стороне проходить внутренние объекты, содержащиеся в IEnumerable-совместимом контейнере:

// This interface allows the caller to
// obtain a container's subitems.
public interface IEnumerator
{
   bool MoveNext (); // Advance the internal position of the cursor.
   object Current { get;} // Get the current item (read-only property).
   void Reset (); // Reset the cursor before the first member.
}

Если вы хотите обновить тип Garage для поддержки этих интерфейсов, вы могли бы пройти долгий путь и реализовать каждый метод вручную. Хотя вы, безусловно, можете предоставить индивидуальные версии GetEnumerator (), MoveNext (), Current и Reset (), есть более простой способ. Поскольку тип System.Array (как и многие другие классы коллекций) уже реализует IEnumerable и IEnumerator, вы можете просто делегировать запрос System.Array следующим образом:

using System.Collections;
...
public class Garage : IEnumerable
{
   // System.Array already implements IEnumerator!
   private Car[] carArray = new Car[4];
   public Garage()
   {
      carArray[0] = new Car("FeeFee", 200);
      carArray[1] = new Car("Clunker", 90);
      carArray[2] = new Car("Zippy", 30);
      carArray[3] = new Car("Fred", 30);
   }
   public IEnumerator GetEnumerator()
   {
      // Return the array object's IEnumerator.
      return carArray.GetEnumerator();
   }
}

После того, как вы обновили свой тип Garage, вы можете безопасно использовать этот тип в конструкции C # foreach. Кроме того, учитывая, что метод GetEnumerator () был определен публично, пользователь объекта может также взаимодействовать с типом IEnumerator:

// Manually work with IEnumerator.
IEnumerator i = carLot.GetEnumerator();
i.MoveNext();
Car myCar = (Car)i.Current;
Console.WriteLine("{0} is going {1} MPH", myCar.PetName, myCar.CurrentSpeed);

Однако, если вы предпочитаете скрыть функциональность IEnumerable от уровня объекта, просто сделайте использование явной реализации интерфейса:

IEnumerator IEnumerable.GetEnumerator()
{
  // Return the array object's IEnumerator.
  return carArray.GetEnumerator();
}

При этом пользователь случайного объекта не найдет метод GetEnumerator () в Garage, а Конструкция foreach при необходимости получит интерфейс в фоновом режиме.

Адаптировано из Pro C # 5.0 и .NET 4.5 Framework

57 голосов
/ 17 февраля 2009

Реализация IEnumerable означает, что ваш класс возвращает объект IEnumerator:

public class People : IEnumerable
{
    IEnumerator IEnumerable.GetEnumerator()
    {
        // return a PeopleEnumerator
    }
}

Реализация IEnumerator означает, что ваш класс возвращает методы и свойства для итерации:

public class PeopleEnumerator : IEnumerator
{
    public void Reset()...

    public bool MoveNext()...

    public object Current...
}

В любом случае это разница.

46 голосов
/ 25 октября 2016

Объяснение через аналогию + Код прохождение игры

Сначала объяснение без кода, потом я добавлю его позже.

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

  1. счетное и
  2. если есть счетчик.

Почему эти требования? Потому что этого требует интерфейс.

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

Что означает счетное?

Если авиакомпания "исчисляема", это означает, что в самолете ДОЛЖЕН присутствовать стюардесса, единственной задачей которой является подсчет - и эта стюардесса ДОЛЖНА вести подсчет весьма специфическим образом:

  1. Счетчик / стюардесса ДОЛЖНА начинать перед первым пассажиром (перед каждым, где они демонстрируют безопасность, как надеть спасательный жилет и т. Д.).
  2. Он / она (т. Е. Стюардесса) ДОЛЖЕН "двигаться дальше" по проходу к первому месту.
  3. Затем он / она должен записать: (i) кто находится на месте, и (ii) его текущее местоположение в проходе.

Процедуры подсчета

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

Таким образом, капитан всегда может получить информацию о текущем человеке, находящемся под следствием. Таким образом, если он узнает, что этому человеку нравится Манчестер Сити, он может предоставить этому пассажиру льготное обращение и т. Д.

  • Счетчик продолжает идти, пока не достигнет конца самолета.

Давайте свяжем это с IEnumerables

  • Перечисляемый - это просто набор пассажиров в самолете. Закон о гражданской авиации - это в основном правила, которым должны следовать все IEnumerables. Каждый раз, когда стюардесса отправляется к капитану с информацией о пассажирах, мы в основном «уступаем» пассажира капитану. Капитан может в основном делать с пассажиром все, что захочет, кроме перестановки пассажиров в самолете. В этом случае им предоставляется льготный режим, если они следуют за Манчестер Сити (тьфу!)

    foreach (Passenger passenger in Plane)
    // the airline hostess is now at the front of the plane
    // and slowly making her way towards the back
    // when she get to a particular passenger she gets some information
    // about the passenger and then immediately heads to the cabin
    // to let the captain decide what to do with it
    { // <---------- Note the curly bracket that is here.
        // we are now cockpit of the plane with the captain.
        // the captain wants to give the passenger free 
        // champaign if they support manchester city
        if (passenger.supports_mancestercity())
        {
            passenger.getFreeChampaign();
        } else
        {
            // you get nothing! GOOD DAY SIR!
        }
    } //  <---- Note the curly bracket that is here!
          the hostess has delivered the information 
          to the captain and goes to the next person
          on the plane (if she has not reached the 
          end of the plane)
    

Резюме

Другими словами, что-то исчисляется, если оно имеет счетчик . И счетчик должен (в основном): (i) запомнить свое место ( состояние ), (ii) быть в состоянии двигаться дальше , (iii) и знать о токе человек, с которым он имеет дело.

Enumerable - это просто причудливое слово для обозначения "исчисляемый". Другими словами, перечислимое позволяет вам «перечислять» (то есть считать).

22 голосов
/ 17 февраля 2009

IEnumerable реализует GetEnumerator. При вызове этот метод возвращает IEnumerator , который реализует MoveNext, Reset и Current.

Таким образом, когда ваш класс реализует IEnumerable, вы говорите, что можете вызвать метод (GetEnumerator) и получить новый возвращенный объект (IEnumerator), который вы можете использовать в цикле, например foreach.

17 голосов
/ 17 февраля 2009

Реализация IEnumerable позволяет вам получить IEnumerator для списка.

IEnumerator обеспечивает последовательный доступ к элементам в списке в стиле foreach с использованием ключевого слова yield.

Перед реализацией foreach (например, в Java 1.4) способ итерации списка заключался в том, чтобы получить перечислитель из списка, а затем запросить у него «следующий» элемент в списке, пока возвращается значение как следующий пункт не является нулевым. Foreach просто делает это неявно в качестве языковой функции, точно так же, как lock () реализует класс Monitor за кулисами.

Я ожидаю, что foreach работает со списками, потому что они реализуют IEnumerable.

15 голосов
/ 17 февраля 2009
  • Объект, реализующий IEnumerable , позволяет другим посещать каждый из его элементов (перечислитель) .
  • Объект, реализующий IEnumerator , выполняет итерацию. Он зацикливается на перечисляемом объекте.

Думайте о перечисляемых объектах как о списках, стеках, деревьях.

11 голосов
/ 03 июня 2017

IEnumerable и IEnumerator (и их общие аналоги IEnumerable и IEnumerator ) являются базовыми интерфейсами итераторов реализаций в .Net Framework Class Libray коллекциях .

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

IEnumerator , с другой стороны, предоставляет интерфейс итерации немного более низкого уровня. Он называется явный итератор , который дает программисту больший контроль над циклом итерации.

IEnumerable

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

foreach Loop

foreach (var value in list)
  Console.WriteLine(value);

Я думаю, что цикл foreach является одной из основных причин использования IEnumerable интерфейсов. foreach имеет очень лаконичный синтаксис и очень прост для понимания по сравнению с классическим стилем C для циклов, где вам нужно проверить различные переменные, чтобы увидеть, что он делает.

ключевое слово yield

Вероятно, менее известной особенностью является то, что IEnumerable также включает генераторы в C # с использованием операторов yield return и yield break.

IEnumerable<Thing> GetThings() {
   if (isNotReady) yield break;
   while (thereIsMore)
     yield return GetOneMoreThing();
}

Абстракция

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

Попался

Следует учитывать одно поведение: при потоковой реализации (например, извлечение данных построчно из базы данных вместо загрузки всех результатов в память вначале) вы не можете выполнять итерацию по коллекции более одного раза , Это отличается от коллекций в памяти, таких как List , где вы можете выполнять многократные итерации без проблем. ReSharper, например, имеет проверку кода для Возможное многократное перечисление IEnumerable .

IEnumerator

IEnumerator, с другой стороны, является скрытым интерфейсом, который заставляет IEnumerble-foreach-magic работать. Строго говоря, он допускает явные итераторы.

var iter = list.GetEnumerator();
while (iter.MoveNext())
    Console.WriteLine(iter.Current);

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

Вариант использования для IEnumerator

Я использовал только IEnumerator в частности (немного более низкого уровня) библиотеки и инфраструктуры, где я предоставлял IEnumerable интерфейсы. Одним из примеров является библиотека обработки потока данных, которая предоставляла серии объектов в цикле foreach, хотя данные за сценой собирались с использованием различных файловых потоков и сериализаций.

Код клиента

foreach(var item in feed.GetItems())
    Console.WriteLine(item);

библиотека

IEnumerable GetItems() {
    return new FeedIterator(_fileNames)
}

class FeedIterator: IEnumerable {
    IEnumerator GetEnumerator() {
        return new FeedExplicitIterator(_stream);
    }
}

class FeedExplicitIterator: IEnumerator {
    DataItem _current;

    bool MoveNext() {
        _current = ReadMoreFromStream();
        return _current != null;           
    }

    DataItem Current() {
        return _current;   
    }
}
8 голосов
/ 17 февраля 2009

Реализация IEnumerable по существу означает, что объект может быть повторен. Это не обязательно означает, что это массив, поскольку есть определенные списки, которые нельзя проиндексировать, но вы можете перечислить их.

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

В большинстве случаев IEnumerable & IEnumerator используются прозрачно как часть цикла foreach.

5 голосов
/ 18 июня 2015

Различия между IEnumerable и IEnumerator:

  • IEnumerable внутренне использует IEnumerator.
  • IEnumerable не знает, какой элемент / объект выполняется.
  • Всякий раз, когда мы передаем IEnumerator другой функции, он знает текущую позицию элемента / объекта.
  • Всякий раз, когда мы передаем коллекцию IEnumerable другой функции, она не знает текущую позицию элемента / объекта (не знает, какой элемент выполняется)

    IEnumerable имеет один метод GetEnumerator ()

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

IEnumerator имеет одно свойство current и два метода Reset и MoveNext (что полезно для определения текущей позиции элемента в списке).

public interface IEnumerator
{
     object Current { get; }
     bool MoveNext();
     void Reset();
}
...