Linq в замочную скважину…

Можете ли вы уверенно сказать, что будет выведено на консоль в результате выполнения следующего кода?

image-loader.svg

Если вы ответили восемь, то, скорее всего, эта заметка не для вас, остальным же предлагаю отправиться вместе со мной в небольшое путешествие в волшебную страну .Net и поближе взглянуть на ее «магию».

Как же получилось 8? Основное удивление может нас постигнуть, если мы попробуем приоткрыть завесу тайны в отладчике:

image-loader.svgimage-loader.svg

Подробно изучая наш bytes в отладчике можно получить и 13 и 23 и т.д.? Что это за чертовщина? :(

Отладчик нам не сильно помог. Быть может все дело в волшебном Where ()? Что будет, если мы (страшно представить), рискнем написать свой собственный Where с дженериками и предикатом и … заменить линковский своим?

Это настолько непосильная задача, что укладывается всего в несколько строк кода:

image-loader.svg

И всё? А разговоров то было…©.

Проверили, работает так же прекрасно, т.е. чертовщина как была так и осталась:

image-loader.svgimage-loader.svg

И где же спрятался наш волшебный гномик? Получается он где-то в нашем собственном Where… давайте присмотримся… Public, static…foreach… Так стоп, а что мы знаем про foreach? Как минимум то, что этот оператор цикла особенный…

Чтобы не тянуть кита за хвост посмотрим на foreach без макияжа сразу на простом примере, развернув С# код с помощью sharplab:

image-loader.svg

То есть эта штука преобразуется здесь в блок try-finaly и беззастенчиво юзает внутри себя «некий IEnumerator»…

Ну, а чем мы хуже?

image-loader.svg

Форич ликвидирован, код стал более понятным и приятным, наш Where работает так же прекрасно.

И раз уж мы начали заменять методы linq своими поделками, то давайте по-быстренькому заменим методы First () и Last () используемые в примере, кустарщиной:

Ать:

image-loader.svg

Два:

image-loader.svg

Проверяем:

image-loader.svg

Давайте разбираться дальше. Итак, foreach — это синтаксический сахар, использующий то, что делает IEnumerable собой:

image-loader.svg

Конечно же с этого нужно было начинать… И о чем я раньше думал! Where возвращает IEnumerable…, а значит нечто, предоставляющее IEnumerator! Вот он проказник!

image-loader.svg

И это всё, что предоставляет IEnumerable? Никаих структур данных, никаких множеств, списков или массиво-образных структур…

Неужто в чистом виде у IEnumerable не существует состояния, а есть лишь поведение, выраженное в методе, предоставляющем некий энумератор?

Для самого энумератора состоянием является единственное поле — сurrent! То есть при обращении к энумератору, даже когда нам кажется, что мы имеем дело с неким множеством, в каждый отдельный момент времени это какой-то один элемент (или ни одного).

На абстрактном примере: если мы вытягиваем шарики из мешка по одному, то IEnumerable — это не мешок и не шарики, а процесс (или подход), при котором в руке единовременно оказывается лишь один шарик. Рука в данном случае — энумератор.

Короче говоря, ошибкой является считать, что IEnumerable — это некое статичное множество. И все становится на свои места, если представить, что IEnumerable — это ДЕЙСТВИЕ (запрос).

И всякий раз когда мы к нему обращаемся, мы это действие запускаем. А теперь на нашем примере:

image-loader.svg

1 — формируем способ выполнения действия (запрос). Это еще не само действие, а только его определение.

2 — метод MyFirst () вызывает действие (обращается к нашему IEnumerable) , которое выполняется ровно до момента, пока это действие методу необходимо, то есть до нахождения единицы. Здесь работает два энумератора. Энумератор метода MyFirst () ожидает предоставления элемента от энумератора IEnumerable bytes. Данный энумератор делает MoveNext () 3 раза, находит первый элемент (1) и отдает его энумератору метода MyFirst (), после чего метод MyFirst () возвращает значение, завершается и потребности во втором энумераторе далее не испытывает. С этого момента действие IEnumerable bytes с точки зрения его инициатора (MyFirst ()) прекращается и второй энумератор получает свой Dispose (). Cчетчик на данном шаге инкрементируется до 3.

3 — метод MyLast () вызывает действие (обращается к нашему IEnumerable) , которое выполняется ровно до момента, пока это действие методу необходимо… (что-то подобное выше мы уже проходили)…то есть до нахождения двойки. Здесь также работает два энумератора. Энумератор метода MyLast () вызывает свой MoveNext () два раза (так как всего два элемента соответствуют предикату). В первый раз это вынудит второй энумератор совершить MoveNext () 3 раза до нахождения единицы. Cчетчик инкрементируется с 3 до 6.

По второму запросу первого энумератора второму энумератору придется совершить еще два MoveNext () до того момента пока он не дойдет с 3 элемента массива до 5 (до конца). Здесь счетчик инкрементируется с 6 до 8.

Волшебство в отладчике объясняется тем, что всякий раз, когда мы пытаемся увидеть результирующие значения IEnumerable bytes щелкая мышкой по ResultsView, мы снова и снова запускаем энумератор, ведь множества как такового не существует и для того, чтобы предоставить результаты выборки нужно совершить ДЕЙСТВИЕ. В этом причина изменений счетчика в отладчике.

Данный аспект еще принято называть отложенным (или ленивым) выполнением (хоть с точки зрения реализации здесь все выполняется тогда, когда предписано кодом).

© Habrahabr.ru