Приветствую всех, сегодня поговорим об интерфейсах IEnumerable и IEnumerator и yield. Прежде чем приступить рассмотрению примеров с интерфейсами, хотел бы показать один пример.
Представьте у нас есть класс Car, а нам нужно узнать все имеющиеся в нем модели автомобилей. Для подобного рода случая сразу хочется применить foreach
1 2 |
foreach(Car model in car) Console.WriteLine(model.Type); |
Однако это будет сделать не возможно, так как класс не реализует интерфейс IEnumerable, соответственно перечисления будет невозможно. Конечно мы можем использовать свойство Car model однако, лучше реализовать интерфейс. Что мы и сделаем! Благодаря интерфейсам IEnumerable и IEnumerator мы можем перебирать объекты в цикле foreach Для этого мы реализуем интерфейс IEnumerable в классе Car. Этот интерфейс описывает всего один метод GetEnumerator(), который возвращает интерфейс IEnumerator. В нашем случаи это может выглядеть так:
1 2 3 4 |
public IEnumerator GetEnumerator() { return Model.GetEnumerator(); } |
Теперь индексатор перенаправляет обращение по индексу к массиву Model, то метод GerEnumerator() может произвести тоже самое. И теперь наш код в самом верху заработает!
Теперь давайте рассмотрим интерфейс IEnumerator, если реализуем в нем интерфейс IEnumerator то увидим три метода.
Current(); Метод должен возвращать текущий элемент списка.
MoveNext(); изменяет счетчик или ссылку на следующий элемент списка
Reset(); Сбросить счетчик.
Практически все эти методы реализуются однотипно, и не составляют труда изменить под свои нужды. По этому я приведу пример того как это можно сделать.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
T[] mass= new T[1]; //обобщенный массив. int position = -1; //позиция перебора начинается с -1 индекса public bool MoveNext() //изменяет счетчик или ссылку на следующий элемент списка { position++; return (position < mass.Length); } public void Reset() //Сбросить счетчик. { position = -1; } public T Current { get { try { return mass[position]; } catch (IndexOutOfRangeException) { throw new InvalidOperationException(); } } } object IEnumerator.Current //Метод должен возвращать текущий элемент списка. { get { return Current; } } |
Теперь достаточно реализовать код и логику программы в методах и использовать в своих целях. Но на прямую редко кто используют интерфейс IEnumerator.
Однако так уже никто не делает, на смену пришел оператор yeild который позволяет облегчить реализацию.
1 2 3 4 5 |
public IEnumerator GetEnumerator() { foreach(Car model in car) yield return model; } |
В таком варианте метода GetEnumerator() мы запускаем цикл, который перебирает все элементы списка. В нашем случаи это происходит еще в одном цикле foreach, но в зависимости от приложения может реализовано по разному. Однако важной особенностью yield является то что он после того как вернул значения, он возвращается обратно в свой метод и продолжается работа метода до завершения его. Стоит так же помнить что yield return не может использоваться внутри блоков обработки исключительных ситуаций.
Ключевое слово yield сообщает компилятору, что метод, в котором оно содержится, является блоком итератора. Для реализации поведения, определенного в блоке итератора, компилятор создает класс. В блоке итератора ключевое слово yield используется совместно с ключевым словом return для предоставления значения объекту перечислителя, например значения, возвращаемого в каждом цикле оператора foreach. Ключевое слово yield всегда используется вместе с ключевым словом break для обозначения конца итерации.
Оператор yield не может использоваться в анонимных методах.
Ниже я привел пример того как можно использовать yield в преобразовании массива в коллекцию.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
static void Main(string[] args) { int[] num = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 }; foreach (var i in MyColection(num)) { Console.WriteLine(i); } Console.ReadKey(); } public static IEnumerable MyColection(int[] arr) { for (int i = 0; i < arr.Length; i++) yield return arr[i]; yield break; } |