Язык программирования C#9 и платформа .NET5
// Вручную работать с IEnumerator.IEnumerator carEnumerator = carLot.GetEnumerator();carEnumerator.MoveNext();Car myCar = (Car)i.Current;Console.WriteLine("{0} is going {1} MPH", myCar.PetName, myCar.CurrentSpeed);Тем не менее, если вы предпочитаете скрыть функциональность
на уровне объектов, то просто задействуйте явную реализацию интерфейса:IEnumerable// Возвратить IEnumerator объекта массива.IEnumerator IEnumerable.GetEnumerator()=> return carArray.GetEnumerator();В результате обычный пользователь объекта не обнаружит метод
в классеGetEnumerator(), в то время как конструкцияGarageпри необходимости будет получать интерфейс в фоновом режиме.foreachПостроение итераторных методов с использованием ключевого слова yield
Существует альтернативный способ построения типов, которые работают с циклом
, предусматривающий использование итераторов. Попросту говоря, итератор — это член, который указывает, каким образом должны возвращаться внутренние элементы контейнера во время обработки в циклеforeach. В целях иллюстрации создайте новый проект консольного приложения по имениforeachи вставьте в него типыCustomEnumeratorWithYield,CarиRadioиз предыдущего примера (снова переименовав пространство имен согласно текущему проекту). Затем модифицируйте типGarage:Garagepublic class Garage : IEnumerable{...<b> // Итераторный метод.</b>public IEnumerator GetEnumerator(){foreach (Car c in carArray){yield return c;}}}Обратите внимание, что показанная реализация метода
осуществляет проход по элементам с применением внутренней логикиGetEnumerator()и возвращает каждый объектforeachвызывающему коду, используя синтаксисCar. Ключевое словоyield returnприменяется для указания значения или значений, которые подлежат возвращению конструкциейyieldвызывающему коду. При достижении оператораforeachтекущее местоположение в контейнере сохраняется и выполнение возобновляется с этого местоположения, когда итератор вызывается в следующий раз.yield returnИтераторные методы не обязаны использовать ключевое слово
для возвращения своего содержимого. Итераторный метод допускается определять и так:foreachpublic IEnumerator GetEnumerator(){yield return carArray[0];yield return carArray[1];yield return carArray[2];yield return carArray[3];}В этой реализации обратите внимание на то, что при каждом своем прохождении метод
явно возвращает вызывающему коду новое значение. В рассматриваемом примере поступать подобным образом мало смысла, потому что если вы добавите дополнительные объекты к переменной-членуGetEnumerator(), то методcarArrayстанет рассогласованным. Тем не менее, такой синтаксис может быть полезен, когда вы хотите возвращать из метода локальные данные для обработки посредствомGetEnumerator().foreachЗащитные конструкции с использованием локальных функций (нововведение в версии 7.0)
До первого прохода по элементам (или доступа к любому элементу) никакой код в методе
не выполняется. Таким образом, если до выполнения оператораGetEnumerator()возникает условие для исключения, то оно не будет сгенерировано при первом вызове метода, а лишь во время первого вызоваyield.MoveNext()Чтобы проверить это, модифицируйте
:GetEnumerator()public IEnumerator GetEnumerator(){// Исключение не сгенерируется до тех пор, пока не будет вызван// метод MoveNext().throw new Exception("This won't get called");foreach (Car c in carArray){yield return c;}}Если функция вызывается, как показано далее, и больше ничего не делается, тогда исключение никогда не сгенерируется:
using System.Collections;...Console.WriteLine("***** Fun with the Yield Keyword *****\n");Garage carLot = new Garage();IEnumerator carEnumerator = carLot.GetEnumerator();Console.ReadLine();Код выполнится только после вызова
и сгенерируется исключение. В зависимости от нужд программы это может быть как вполне нормально, так и нет. Ваш методMoveNext()может иметь защитную конструкцию, которую необходимо выполнить при вызове метода в первый раз. В качестве примера предположим, что список формируется из базы данных. Вам может понадобиться организовать проверку, открыто ли подключение к базе данных, во время вызова метода, а не при проходе по списку. Или же может возникнуть потребность в проверке достоверности входных параметров методаGetEnumerator(), который рассматривается далее.Iterator()