Подтвердить что ты не робот

Выполняет ли foreach запрос только один раз?

У меня есть список элементов и запрос LINQ над ними. Теперь, с отложенным выполнением LINQ, будет ли следующий цикл foreach выполнять запрос только один раз или для каждого поворота в цикле?

Учитывая этот пример (взято из Введение в запросы LINQ (С#) в MSDN)

    // The Three Parts of a LINQ Query: 
    //  1. Data source. 
    int[] numbers = new int[7] { 0, 1, 2, 3, 4, 5, 6 };

    // 2. Query creation. 
    // numQuery is an IEnumerable<int> 
    var numQuery =
        from num in numbers
        where (num % 2) == 0
        select num;

    // 3. Query execution. 
    foreach (int num in numQuery)
    {
        Console.Write("{0,1} ", num);
    }

Или, другими словами, была бы какая-то разница, если бы я имел:

    foreach (int num in numQuery.ToList())

И, было бы важно, если базовые данные не находятся в массиве, а в базе данных?

4b9b3361

Ответ 1

Теперь, с отложенным выполнением LINQ, будет ли следующий цикл foreach выполнять запрос только один раз или для каждого поворота в цикле?

Да, один раз для цикла. Фактически, он может выполнить запрос менее одного раза - вы могли бы прервать прохождение цикла, и тест (num % 2) == 0 не будет выполняться ни в каких остальных элементах.

Или, другими словами, была бы какая-то разница, если бы у меня было:

foreach (int num in numQuery.ToList())

Две отличия:

  • В вышеприведенном случае ToList() тратит время и память, потому что сначала выполняет ту же самую вещь, что и исходный foreach, строит список из него, а затем foreach этот список. Различия будут где-то между тривиальными и предотвращать когда-либо работающий код в зависимости от размера результатов.

  • Однако в том случае, когда вы собираетесь повторно делать foreach по тем же результатам или иным образом использовать его повторно, тогда, когда foreach запускает запрос один раз, следующий foreach снова запускает его. Если запрос стоит дорого, то подход ToList() (и сохранение этого списка) может быть огромной экономией.

Ответ 2

Нет, это не имеет значения. Выражение in оценивается один раз. Более конкретно, конструкция foreach вызывает метод GetEnumerator() в выражении in и повторно вызывает MoveNext() и обращается к свойству Current для прохождения IEnumerable.

OTOH, вызов ToList() является избыточным. Вы не должны называть его.

Если входной файл является базой данных, ситуация немного отличается, поскольку LINQ выводит IQueryable, но я уверен, что foreach по-прежнему рассматривает его как IEnumerable (который наследует IQueryable).

Ответ 3

Как написано, каждая итерация цикла будет выполнять столько же работы, сколько потребовалось бы для получения следующего результата. Таким образом, ответ был бы технически "ни одним из вышеперечисленных". Запрос будет выполняться "в кусках".

Если вы используете ToList() или любой другой метод материализации (ToArray() и т.д.), тогда запрос будет оцениваться один раз на месте, а последующие операции (например, итерация по результатам) будут просто работать в "тупиком" списке.

Если numbers были IQueryable вместо IEnumerable - как это было бы в сценарии базы данных, то приведенное выше по-прежнему близко к истине, хотя и не является точным описанием. В частности, при первой попытке материализовать результат, запрашиваемый провайдер будет разговаривать с базой данных и создавать набор результатов; тогда строки из этого результирующего набора будут натягиваться на каждую итерацию.

Ответ 4

Запрос linq будет выполняться при его перечислении (либо в результате вызова .ToList(), либо при выполнении foreach по результатам.

Если вы дважды перечисляете результаты запроса linq, оба раза заставят его запросить источник данных (в вашем примере перечисление коллекции), поскольку он сам возвращает только IEnumerable. Однако, в зависимости от запроса linq, он может не всегда перечислять всю коллекцию (например, .Any() и .Single() останавливается на первом объекте или первом соответствующем объекте, если есть .Where()).

Сведения о реализации провайдера linq могут отличаться, поэтому обычное поведение, когда источником данных является база данных, - это вызвать .ToList() прямо на cache результаты запроса, а также убедиться, что запрос (в случае из EF, L2S или NHibernate) выполняется один раз там, а не тогда, когда сборка перечислится в какой-то момент позже в коде и для предотвращения выполнения запроса несколько раз, если результаты перечислены несколько раз.