Почему методы расширения Linq не помещаются на IEnumerator, а не на IEnumerable?
Существует множество алгоритмов Linq, которые нужно выполнить только один проход через вход, например. Выбрать.
Однако все методы расширения Linq расположены на IEnumerable, а не на IEnumerator
var e = new[] { 1, 2, 3, 4, 5 }.GetEnumerator();
e.Select(x => x * x); // Doesn't work
Это означает, что вы не можете использовать Linq в любой ситуации, когда вы читаете из "уже открытого" потока.
Этот сценарий очень много работает для проекта, над которым я сейчас работаю - я хочу вернуть IEnumerator, метод IDispose которого закроет поток, и на нем будет работать весь нижеперечисленный код Linq.
Короче говоря, у меня есть "уже открытый" поток результатов, который я могу преобразовать в подходящий одноразовый IEnumerator, но, к сожалению, для всего нижестоящего кода требуется IEnumerable, а не IEnumerator, хотя он будет делать только один ", пройти".
то есть. Я хочу "реализовать" этот тип возврата в различных источниках (файлы CSV, IDataReaders и т.д.):
class TabularStream
{
Column[] Columns;
IEnumerator<object[]> RowStream;
}
Чтобы получить "Столбцы", я должен был открыть файл CSV, инициировал SQL-запрос или что-то еще. Затем я могу вернуть "IEnumerator", метод Dispose которого закрывает ресурс, но для всех операций Linq требуется IEnumerable.
Лучшим обходным решением, которое я знаю, является реализация IEnumerable, метод GetEnumerator() возвращает одномерный IEnumerator и выдает ошибку, если что-то пытается дважды выполнить вызов GetEnumerator().
Все это звучит нормально или есть намного лучший способ реализовать "TabularStream" таким образом, который легко использовать из Linq?
Ответы
Ответ 1
Использование IEnumerator<T>
напрямую редко является хорошей идеей, на мой взгляд.
Во-первых, он кодирует тот факт, что он разрушительный, тогда как запросы LINQ обычно запускаются несколько раз. Они должны быть свободными от побочных эффектов, в то время как действие итерации по IEnumerator<T>
естественно является побочным эффектом.
Это также делает практически невозможным выполнение некоторых из оптимизаций в LINQ для объектов, например использование свойства Count
, если вы действительно запрашиваете ICollection<T>
для его подсчета.
Что касается вашего обходного пути: да, OneShotEnumerable
будет разумным подходом.
Ответ 2
Пока я вообще согласен с ответом Jon Skeet, я также сталкивался с очень немногими случаями, когда работа с IEnumerator
действительно казалась более подходящей, чем упаковка их в один раз только- IEnumerable
.
Я начну с иллюстрации одного такого случая и описав свое собственное решение проблемы.
Пример примера: прямые, неперематываемые курсоры баз данных только вперед,
ESRI API для доступа к геоданным (ArcObjects) содержит курсоры баз данных, которые не могут быть reset. Они по сути являются эквивалентом API IEnumerator
. Но нет эквивалента IEnumerable
. Поэтому, если вы хотите обернуть этот API в "путь .NET", у вас есть три варианта (которые я изучил в следующем порядке):
-
Оберните курсор как IEnumerator
(так как это действительно так) и работайте напрямую с этим (что громоздко).
-
Оберните курсор или обертку IEnumerator
из (1) как только один раз IEnumerable
(чтобы сделать его совместимым с LINQ и, как правило, более удобным для работы). Ошибка здесь в том, что она не является IEnumerable
, потому что ее нельзя перечислить более одного раза, и это может быть упущено пользователями или сторонниками вашего кода.
-
Не помещайте курсор в качестве IEnumerable
, а тот, который может использоваться для извлечения курсора (например, критерии запроса и ссылка на запрашиваемый объект базы данных). Таким образом, несколько итераций возможно просто повторно выполнить весь запрос. Это то, что я в конечном итоге решил тогда.
Этот последний вариант - это прагматическое решение, которое я обычно рекомендую для подобных случаев (если применимо). Если вы ищете другие решения, читайте дальше.
Повторно реализовать операторы запросов LINQ для интерфейса IEnumerator<T>
?
Технически возможно реализовать некоторые или все операторы запроса LINQ для интерфейса IEnumerator<T>
. Один из подходов - написать кучу методов расширения, таких как:
public static IEnumerator<T> Where(this IEnumerator<T> xs, Func<T, bool> predicate)
{
while (xs.MoveNext())
{
T x = xs.Current;
if (predicate(x)) yield return x;
}
yield break;
}
Рассмотрим несколько ключевых вопросов:
-
Операторы никогда не должны возвращать IEnumerable<T>
, потому что это может означать, что вы можете вырваться из своего собственного "LINQ to IEnumerator
" мира и выйти в обычный LINQ. Там вы столкнулись с проблемой не повторяемости, уже описанной выше.
-
Вы не можете обрабатывать результаты какого-либо запроса с помощью цикла foreach
& hellip; если каждый из объектов IEnumerator<T>
, возвращаемых вашими операторами запроса, реализует метод GetEnumerator
, который возвращает this
. Поставка этого дополнительного метода означала бы, что вы не можете использовать yield return/break
, но должны писать классы IEnumerator<T>
вручную.
Это просто странно и возможно злоупотребление конструкцией IEnumerator<T>
или foreach
.
-
Если возврат IEnumerable<T>
запрещен и возврат IEnumerator<T>
является громоздким (потому что foreach
не работает), почему бы не вернуть простые массивы? Потому что тогда запросы больше не могут лениться.
IQueryable
+ IEnumerator
= IQueryator
Как насчет задержки выполнения запроса, пока он не будет полностью скомпонован? В мире IEnumerable
это то, что делает IQueryable
; поэтому мы теоретически могли бы построить эквивалент IEnumerator
, который я назову IQueryator
.
-
IQueryator
может проверять логические ошибки, например делать что-либо с последовательностью после того, как она полностью поглощена предыдущей операцией, например Count
. То есть всепоглощающие операторы типа Count
всегда должны были быть последними в конкатенации оператора запроса.
-
IQueryator
может возвращать массив (например, предложенный выше) или какую-либо другую коллекцию только для чтения, но не отдельными операторами; только при выполнении запроса.
Реализация IQueryator
займет довольно много времени... вопрос в том, действительно ли это стоило бы усилий?