LINQ Skip все еще перечисляет пропущенные объекты
В следующем тесте:
int[] data = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
Func<int, int> boom = x => { Console.WriteLine(x); return x; };
var res = data.Select(boom).Skip(3).Take(4).ToList();
Console.WriteLine();
res.Select(boom).ToList();
Результат:
1
2
3
4
5
6
7
4
5
6
7
По существу, я заметил, что в этом примере Skip()
и Take()
работают хорошо, Skip()
не так ленив, как Take(). Кажется, что Skip()
все еще перечисляет элементы, пропущенные, даже если они не возвращают их.
То же самое относится, если я сначала Take()
. Мое лучшее предположение состоит в том, что ему нужно перечислить, по крайней мере, первый пропустить или принять, чтобы посмотреть, куда идти со следующей.
Почему он это делает?
Ответы
Ответ 1
Skip()
и Take()
работают на IEnumerable<>
.
IEnumerable<>
не поддерживает пропуски вперед - он может давать только один элемент за раз. Имея это в виду, вы можете думать о Skip()
больше как о фильтре - он по-прежнему касается всех элементов в исходной последовательности, но он отфильтровывает все, о чем вы говорите. И что важно, он отфильтровывает их от получения до следующего, а не того, что перед ним.
Итак, сделав это:
data.Select(boom).Skip(3)
Вы выполняете boom()
для каждого элемента перед тем, как перейти к фильтру Skip()
.
Если вы изменили его на это, он будет фильтровать до Select
, и вы вызываете boom()
только на оставшиеся элементы:
data.Skip(3).Take(4).Select(boom)
Ответ 2
Если вы декомпилируете Enumerable
, вы увидите следующую реализацию Skip
:
while (count > 0 && e.MoveNext())
--count;
и следующую реализацию Take
:
foreach (TSource source1 in source)
{
yield return source1;
if (--count == 0) break;
}
Итак, оба этих метода LINQ фактически перечисляют эти элементы. Разница заключается в том, будет ли перечисленный элемент помещен в результирующую коллекцию или нет. То, как работает IEnumerable
.
Ответ 3
Они итерации коллекции. А затем явка с окончательной коллекцией. Его просто как простой for loop
, имеющий в нем условие if else
.
Кроме того, вы выбираете его сначала, используя boom
, он печатает элемент коллекции.
В этом примере вы не можете сказать, выполняет ли skip()
или take()
всю коллекцию или нет, но дело в том, что они делают.
Ответ 4
Предполагая, что я правильно понял ваш вопрос.
функция прыжка выполняется перед выполнением операции пропуска.
попробуйте выбрать данные после пропустить и взять, вы получите точное.
Ответ 5
Такое поведение, возможно, будет изменено в будущем, как можно видеть здесь в этом обсуждении о запросе Pull, который оптимизирует поведение Пропустить метод для источников, реализующих интерфейс IList.
Ответ 6
Не ответ, но подумайте о том, как Skip(...)
будет реализован. Вот один из способов сделать это:
public static IEnumerable<T> Skip<T>(this IEnumerable<T> list, int count)
{
foreach (var item in list)
{
if (count > 0) count--;
else yield return item;
}
}
Обратите внимание, что весь аргумент list
указан, хотя возвращается только подмножество.