Lazy LINQ

Подивившись жизненной несправедливости в прошлый раз, я задумался, что прикрутить ленивую загрузку в LINQ запрос должно быть не очень-то и сложно.

Основная идея проста: сделать дополнительный метод расширения, который бы выдавал не сразу данные по yield return, а возвращал бы какой-то объект, кеширующий после первого обращения данные. Отложенность загрузки хотелось сохранить, грузить при первом обращении всю последовательность также не хотелось. Поэтому я создал class LazyEnumerable<T>: IEnumerable<T>, в котором хранил исходную последовательность, объект-перечислитель для нее и уже загруженные данные в виде обычного List<T>.

Вся логика была заключена в собственном объекте-перечислителе (enumerator) для этой последовательности. В конструктор энумератора я передавал исходную энумератор исходной последовательности и список уже загруженных элементов. Сперва я при обращении выдавал все элементы кеша, а когда они кончались, начинал выдавать элементы последовательности, заполняя попутно кеш:

    public bool MoveNext()
    {
        if (!_cacheEnded && _cacheEnumerator.MoveNext())
        {
            return true// Идем по сохраненной последовательности
        }
        else
        {
            // Сохраненная последовательность кончилась, идем по основноц
            _cacheEnded = true// Закончился кеш
            bool res = _enumerator.MoveNext();
            if (res) _cache.Add(this.Current);
            return res;
        }
    }

    public T Current     {         get          {             if (!_cacheEnded) return _cacheEnumerator.Current;             else return _enumerator.Current;

         }
}

При реализации в очередной раз порадовался, насколько шаблоны проектирования облегчают разработку. Идею можно сформулировать в понятной для любого разработчика формулировке: "Выдавать сперва энумератор сохраненной, а потом исходной последовательности." Хотя надо заметить, что получившийся код получился не супер-читаемым как минимум из-за того, что пришлось реализовывать и IEnumerator<T> и IEnumerator. А это еще не добавлена потокобезопасность, например. Так что подобные методы массово писать без реальной необходимости я бы не стал.

Исходные коды для этого поста можно скачать отсюда.