Linq в замочную скважину…
Можете ли вы уверенно сказать, что будет выведено на консоль в результате выполнения следующего кода?
Если вы ответили восемь, то, скорее всего, эта заметка не для вас, остальным же предлагаю отправиться вместе со мной в небольшое путешествие в волшебную страну .Net и поближе взглянуть на ее «магию».
Как же получилось 8? Основное удивление может нас постигнуть, если мы попробуем приоткрыть завесу тайны в отладчике:
Подробно изучая наш bytes в отладчике можно получить и 13 и 23 и т.д.? Что это за чертовщина? :(
Отладчик нам не сильно помог. Быть может все дело в волшебном Where ()? Что будет, если мы (страшно представить), рискнем написать свой собственный Where с дженериками и предикатом и … заменить линковский своим?
Это настолько непосильная задача, что укладывается всего в несколько строк кода:
И всё? А разговоров то было…©.
Проверили, работает так же прекрасно, т.е. чертовщина как была так и осталась:
И где же спрятался наш волшебный гномик? Получается он где-то в нашем собственном Where… давайте присмотримся… Public, static…foreach… Так стоп, а что мы знаем про foreach? Как минимум то, что этот оператор цикла особенный…
Чтобы не тянуть кита за хвост посмотрим на foreach без макияжа сразу на простом примере, развернув С# код с помощью sharplab:
То есть эта штука преобразуется здесь в блок try-finaly и беззастенчиво юзает внутри себя «некий IEnumerator
Ну, а чем мы хуже?
Форич ликвидирован, код стал более понятным и приятным, наш Where работает так же прекрасно.
И раз уж мы начали заменять методы linq своими поделками, то давайте по-быстренькому заменим методы First () и Last () используемые в примере, кустарщиной:
Ать:
Два:
Проверяем:
Давайте разбираться дальше. Итак, foreach — это синтаксический сахар, использующий то, что делает IEnumerable
Конечно же с этого нужно было начинать… И о чем я раньше думал! Where возвращает IEnumerable
И это всё, что предоставляет IEnumerable
Неужто в чистом виде у IEnumerable
Для самого энумератора состоянием является единственное поле — сurrent! То есть при обращении к энумератору, даже когда нам кажется, что мы имеем дело с неким множеством, в каждый отдельный момент времени это какой-то один элемент (или ни одного).
На абстрактном примере: если мы вытягиваем шарики из мешка по одному, то IEnumerable
Короче говоря, ошибкой является считать, что IEnumerable
И всякий раз когда мы к нему обращаемся, мы это действие запускаем. А теперь на нашем примере:
1 — формируем способ выполнения действия (запрос). Это еще не само действие, а только его определение.
2 — метод MyFirst () вызывает действие (обращается к нашему IEnumerable
3 — метод MyLast () вызывает действие (обращается к нашему IEnumerable
По второму запросу первого энумератора второму энумератору придется совершить еще два MoveNext () до того момента пока он не дойдет с 3 элемента массива до 5 (до конца). Здесь счетчик инкрементируется с 6 до 8.
Волшебство в отладчике объясняется тем, что всякий раз, когда мы пытаемся увидеть результирующие значения IEnumerable
Данный аспект еще принято называть отложенным (или ленивым) выполнением (хоть с точки зрения реализации здесь все выполняется тогда, когда предписано кодом).