2014-08-29 4 views
6

Скажем, у нас есть list.Where(p=>p.Number > n).Select(p=> p.Name).Where(n=> n.StartsWith(a)).ToList();, будет ли этот алгоритм пройден один проход или это будет 3 прохода?LINQ пытается решить все за один проход?

+0

Скажем, у нас есть государственный экзамен, и ученикам предлагается решить простую алгоритмическую задачу, которая может быть решена с помощью C# LINQ в одном приятном, простом запросе. Тем не менее, если они не могут доказать за один проход за один проход, это будет считаться неоптимальным решением, и студенты получат плохие оценки. – Rella

+0

Обратите внимание, что существует аналогичный вопрос re Linq2SQL: будет ли результирующий запрос выполняться с использованием одного оператора SELECT? Для простых запросов, подобных вашему примеру, если они могут быть преобразованы в SQL-запрос, ответ будет да. Но некоторые [complex] (http://stackoverflow.com/q/22816591/256431) [запросы] (http://stackoverflow.com/q/12264751/256431) этого не делают, и я не знаю, если любая гарантия предоставляется. –

ответ

6

Он будет создавать список за один проход, из-за того, что 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.

8

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(); 

Если код генерирует исключение, вы пытались повторять базовый список несколько раз. Если нет, вы этого не сделали.