Итераторы и yield в C#

Добавлен , опубликован
Раздел:
C#
Итератор — это объект, позволяющий перемещаться (итерироваться) по элементам некоторой последовательности. По природе своей итератор — это поведенческий шаблон проектирования.
Почти каждой написанной вами программе придется выполнять итерацию определенной коллекции. Для этого вы напишете код, проверяющий каждый элемент в коллекции.
Кроме того, вы создадите методы итератора — методы, которые итератор создает для элементов соответствующего класса. Их можно использовать в следующих целях:
• Выполнение определенного действия с каждым элементом в коллекции.
• Перечисление настраиваемой коллекции.
• Расширение LINQ или других библиотек.
• Создание конвейера данных, обеспечивающего эффективный поток данных через методы итератора.
К примеру, у вас есть коллекция, которая постоянно заполняется данными, но нам необходимо извлечь из нее содержимое по заданным критериям. Вариантов это сделать несколько, но сейчас разберем примеры с итераторами.
Для начала создадим тестовую площадку с примитивным индексируемым объектом:

    public class Item
    {
        private readonly int id;
        public int Id => id;

        public Item(int id) => this.id = id;
        public void Print() => Console.WriteLine(id);
    }
Если все сделано правильно, то на консоли будет единица при запуске такого метода в приложении:
    static void Main()
    {
        var example = new Item(1);
        example.Print();

        Console.Read();
    }
Теперь можем сделать метод Main так, чтобы можно было перебрать все элементы списка с последующим выводом их индексов на консоли:
    static void Main()
    {
        List<Item> example = new List<Item>
        {
            new Item(-2),
            new Item(-1),
            new Item(0),
            new Item(1),
            new Item(2)
        };

        foreach (Item item in example)
            item.Print();

        Console.Read();
    }
Вывод: -2 -1 0 1 2

foreach

Оператор foreach разворачивается в стандартную идиому, которая выполняет итерацию всех элементов в коллекции с помощью интерфейсов IEnumerable<T> и IEnumerator<T>. Кроме того, он сводит к минимуму ошибки, допускаемые разработчиками в результате неправильного управления ресурсами.
Компилятор преобразует цикл foreach, показанный в первом примере, в конструкцию следующего вида:
(C# 5.0)
IEnumerator<int> enumerator = collection.GetEnumerator();
while (enumerator.MoveNext())
{
    var item = enumerator.Current;
    item.Print();
}
Другими словами каждый объект, который наследуется и реализовывает интерфейс IEnumerable, может использоваться с оператором foreach

yield retun / break / wtf?

yield — ключевое слово, которое своим присутствием в методе / операторе / аксессоре означает принадлежность к итератору. Иными словами все, в чем находится данное ключевое слово, и есть итератор.
Самый простой пример, который заменяет коллекцию из нашего приложения на итератор с ключевым словом yield:
    public static IEnumerable<Item> GetAllItems()
    {
        yield return new Item(-2);
        yield return new Item(-1);
        yield return new Item(0);
        yield return new Item(1);
        yield return new Item(2);
    }


    static void Main()
    {
        IEnumerable<Item> example = GetAllItems();

        foreach (Item item in example)
            item.Print();

        Console.Read();
    }
И аналогичным результатом будет: -2 -1 0 1 2
Оператор yield return используется для возврата каждого элемента по одному.
Последовательность, которая возвращается после выполнения метода итератора, можно использовать с помощью оператора foreach или запроса LINQ. Каждая итерация цикла foreach вызывает метод итератора. При достижении в методе итератора оператора yield return возвращается expression и сохраняется текущее расположение в коде. При следующем вызове функции итератора выполнение возобновляется с этого места.
Для завершения итерации можно использовать оператор yield break.
Ниже приведен полноценный пример работы итератора.
Мы создаем объект, в конструкторе которого с вероятностью 40% может создастся два дочерних объекта с номером индекса +100 и +101 от родительского. Наша задача - вывести все индексы всех дочерних объектов на консоль:
using System;
using System.Collections.Generic;

class Program
{
    public class Item
    {
        private readonly int id;
        public int Id => id;

        public Item(int id)
        {
            this.id = id;

            Children = new List<Item>();

            int chance = new Random().Next(0, 100);

            if (chance <= 40) // шанс срабатывания на добавления двух "детей"
            {
                Children.Add(new Item(id + 100));
                Children.Add(new Item(id + 101));
            }
        }

        public void Print() => Console.WriteLine(id);

        List<Item> Children;

        public IEnumerable<Item> GetChildren() // итератор, собирающих посредством yield return все дочерние элементы
        {
            if (Children.Count <= 0) yield break;
            
            foreach (var child in Children)
            {
                yield return child; // просто возвращаем дочерние элементы
                // мы не забыли о том, что у дочерних элементов тоже может быть два дочерних элемента, что обрабатывается ниже
                foreach (var subchild in child.GetChildren()) // рекурсивно вызываем метод аггрегации дочерних элементов у текущих дочерних элементов
                {
                    yield return subchild;
                }
            }
        }
    }

    static void Main()
    {
        foreach (Item item in new Item(0).GetChildren())
            item.Print();

        Console.Read();
    }
}

Исключения:

Оператор yield return нельзя размещать в блоке try-catch. Оператор yield return можно размещать в блоке try оператора try-finally.
Оператор yield break можно размещать в блоке try или catch, но не в блоке finally.
Если тело оператора foreach (вне метода итератора) вызывает исключение, выполняется блок finally в методе итератора.
За сим все. Спасибо за внимание
`
ОЖИДАНИЕ РЕКЛАМЫ...
2
22
4 года назад
2
Хорошая статья
2
29
4 года назад
2
Если кому не сложно, то напишите, какие бы статьи были бы вам интересны. Пока есть свободное время, попробую написать парочку
2
22
4 года назад
2
Асинхронность на .net 4.0 и 4.5> очень будет интересна
0
20
4 года назад
0
На мой взгляд стоит добавить к описанию оператора yield пример того, во что он превращается на самом деле, как [здесь]
0
7
4 года назад
0
+1 за асинхронность
Чтобы оставить комментарий, пожалуйста, войдите на сайт.