Засада с LINQ
В процессе реализации программы, о которой я писал в прошлом посте, я напоролся на одну забавную засаду.
Дело в том, что слова из файла я получаю так (упрощенно):
24 while (true)
25 {
26 var word = file.ReadLine();
27 if (word.Length == length) yield return word; // нашли слово нужной длины
28 }
Как оказалось, надо было сразу полезть вглубь отладки, чтобы разобраться, что к чему, так как аналогичная проблема вылезла очень скоро, но в гораздо более запутанном месте, при вот этом вызове:
59 generation = generation.SelectMany(m => Step(m)); // Получим следующее поколение
Казалось бы обычное получение набора элементов следующего поколения. Но хотя эта команда давала правильные на первый взгляд результаты, любое следующее обращение к этим элементам выдавало пустое множество! То есть если вывести значение .Count() в консоль, то выдавалось число элементов, а если потом поглядеть результаты в отладке, то значений не было. Если выдать значение .Count() два раза в консоль, то во втором случае печатался 0!
Я впал в задумчивость, а потом в глубокую отладку. Благо рефлектор позволяет отладчиком трассировать внутренние вызовы стандартной библиотеки. Отладкой я быстро (часа через полтора) нашел корень зла: оказалось, что никакого lazy load в LINQ нет. Точнее есть lazy, а нет load. :) То есть данные не загружаются при создании LINQ-запроса, но даже когда требуются значения, никакого сохранения данных для ускорения последующего доступа не происходит! То есть мой метод .Step() (а ранее метод чтения из файла) вызывался каждый раз при обращении к набору. А это все ломало, так как внутри этого метода правились элементы колекции так, что повторно они не выбирались!
Так что я вписал второй вызов .ToArray() и получил работающую программу, а также бесценный опыт и философский взгляд на жизнь.