Почему Skip () в LINQ для объектов не оптимизирован? - PullRequest
5 голосов
/ 05 июня 2011
var res = new int[1000000].Skip(999999).First();

Было бы замечательно, если бы этот запрос использовал индексатор, а не проходил 999999 записей.

Я заглянул в System.Core.dll и заметил, что в отличие от Skip(), метод расширения Count() оптимизирован. Если IEnumerable реализует ICollection, то он просто вызывает свойство Count.

Ответы [ 2 ]

3 голосов
/ 05 июня 2011

Если вы посмотрите на мой ответ на похожий вопрос, то создается впечатление, что должно быть легко обеспечить не наивную (т. Е. Выбрасывать правильные исключения) оптимизацию Skip для любого IList :

    public static IEnumerable<T> Skip<T>(this IList<T> source, int count)
    {
        using (var e = source.GetEnumerator())
            while (count < source.Count && e.MoveNext())
                yield return source[count++];
    }

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

3 голосов
/ 05 июня 2011

Я позволю Джону Скиту ответить на этот вопрос:

Если наша последовательность является списком, мы можем просто пропустить ее до правой части и выдавать элементы по одному. Это звучит замечательно, но что, если список изменится (или даже обрезается!), Пока мы будем перебирать его? Реализация, работающая с простым итератором, обычно выдает исключение, поскольку изменение делает итератор недействительным. Это определенно поведенческие изменения. Когда я впервые написал о Skip, я включил это как «возможную» оптимизацию - и фактически включил ее в исходный код Edulinq. Теперь я считаю это ошибкой и полностью удалил ее.

...

Проблема обеих этих «оптимизаций» заключается в том, что они применяют оптимизацию на основе списка в блоке итератора, который используется для отложенного выполнения. Оптимизация для списков либо в начальной точке вызова метода, либо в пределах непосредственного оператора выполнения (Count, ToList и т. Д.) - это хорошо, потому что мы предполагаем, что последовательность не изменится в ходе выполнения метода. Мы не можем сделать это предположение с блоком итератора, потому что поток кода сильно отличается: наш код посещается многократно в зависимости от использования вызывающей стороны MoveNext ().

https://msmvps.com/blogs/jon_skeet/archive/2011/01/26/reimplementing-linq-to-objects-part-40-optimization.aspx

...