Скажем, у нас есть list.Where(p=>p.Number > n).Select(p=> p.Name).Where(n=> n.StartsWith(a)).ToList();
, будет ли этот алгоритм пройден один проход или это будет 3 прохода?LINQ пытается решить все за один проход?
ответ
Он будет создавать список за один проход, из-за того, что LINQ потоков данных.
Например, возьмите это:
var query = list.Where(p => p.Number > n);
Это само по себе не смотрит на любой из элементов списка. Вместо этого он запоминает список, на который вы смотрите, и когда вы начинаете итерацию более query
, каждый раз, когда вы запрашиваете следующий элемент, он будет проверять элементы списка по очереди, пока не найдет совпадение, а затем остановится. Например:
using (var iterator = query.GetEnumerator())
{
iterator.MoveNext(); // This will look for the first match
Console.WriteLine(iterator.Current);
iterator.MoveNext(); // This will continue from just after the first match
}
Каждая из операций работает таким образом - так что к тому времени, когда вы получили:
var query = list.Where(...)
.Select(...)
.Where(...);
... когда вы просите за первый пункт в query
, он будет цепь (так что последний Where
спросит результат Select
, который спросит результат первого Where
, который будет запрашивать список) и продолжайте движение, пока не получите результат. Затем, когда вы запрашиваете к следующему пункту, который будет просить результат Select
для следующего пункта и т.д.
ToList
строит List<T>
из всех элементов в его источнике, сразу - это нетерпеливого в этом смысле (а не другие операторы здесь ленивый). Но исходный список по-прежнему будет повторяться только один раз.
Для лот более подробно о том, как работает LINQ to Objects - включая пример реализации - вы можете прочитать мой Edulinq blog series.
list
будет повторяться только один раз в этом коде, а не 3 раза.
Конечно, если вы хотите проверить, если любой произвольный запрос итерации по источникам несколько раз достаточно легко экспериментально проверить, просто создать IEnumerable
что вызывает исключение при попытке итерации его несколько раз:
public static IEnumerable<T> ThereCanBeOnlyOne<T>(this IEnumerable<T> source)
{
return new SingleEnumerable<T>(source);
}
private class SingleEnumerable<T> : IEnumerable<T>
{
private bool hasRun = false;
private IEnumerable<T> wrapped;
public SingleEnumerable(IEnumerable<T> wrapped)
{
this.wrapped = wrapped;
}
public IEnumerator<T> GetEnumerator()
{
if (hasRun)
throw new InvalidOperationException(
"Sequence cannot be enumerated multilpe times");
else
{
hasRun = true;
return wrapped.GetEnumerator();
}
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
Теперь вы можете просто написать:
list.ThereCanBeOnlyOne()
.Where(p=>p.Number > n)
.Select(p=> p.Name)
.Where(n=> n.StartsWith(a))
.ToList();
Если код генерирует исключение, вы пытались повторять базовый список несколько раз. Если нет, вы этого не сделали.
Скажем, у нас есть государственный экзамен, и ученикам предлагается решить простую алгоритмическую задачу, которая может быть решена с помощью C# LINQ в одном приятном, простом запросе. Тем не менее, если они не могут доказать за один проход за один проход, это будет считаться неоптимальным решением, и студенты получат плохие оценки. – Rella
Обратите внимание, что существует аналогичный вопрос re Linq2SQL: будет ли результирующий запрос выполняться с использованием одного оператора SELECT? Для простых запросов, подобных вашему примеру, если они могут быть преобразованы в SQL-запрос, ответ будет да. Но некоторые [complex] (http://stackoverflow.com/q/22816591/256431) [запросы] (http://stackoverflow.com/q/12264751/256431) этого не делают, и я не знаю, если любая гарантия предоставляется. –