Отложенное и немедленное выполнение LINQ

Последнее обновление: 15.02.2022

Есть два способа выполнения запроса LINQ: отложенное (deferred) и немедленное (immediate) выполнение.

При отложенном выполнении LINQ-выражение не выполняется, пока не будет произведена итерация или перебор по выборке, например, в цикле foreach. Обычно подобные операции возвращают объект IEnumerable<T> или IOrderedEnumerable<T>. Полный список отложенных операций LINQ:

  • AsEnumerable

  • Cast

  • Concat

  • DefaultIfEmpty

  • Distinct

  • Except

  • GroupBy

  • GroupJoin

  • Intersect

  • Join

  • OfType

  • OrderBy

  • OrderByDescending

  • Range

  • Repeat

  • Reverse

  • Select

  • SelectMany

  • Skip

  • SkipWhile

  • Take

  • TakeWhile

  • ThenBy

  • ThenByDescending

  • Union

  • Where

Рассмотрим отложенное выполнение:

string[] people = { "Tom", "Sam", "Bob" };

var selectedPeople = people.Where(s=>s.Length == 3).OrderBy(s=>s);

// выполнение LINQ-запроса
foreach (string s in selectedPeople)
    Console.WriteLine(s);

То есть фактическое выполнение запроса происходит не в строке определения: var selectedPeople = people.Where..., а при переборе в цикле foreach.

Фактически LINQ-запрос разбивается на три этапа:

  1. Получение источника данных

  2. Создание запроса

  3. Выполнение запроса и получение его результатов

Как это происходит в нашем случае:

  1. Получение источника данных - определение массива teams:

    string[] people = { "Tom", "Sam", "Bob" };
  2. Создание запроса - определение переменной selectedTeams:

    var selectedPeople = people.Where(s=>s.Length == 3).OrderBy(s=>s);
  3. Выполнение запроса и получение его результатов:

    foreach (string s in selectedPeople)
        Console.WriteLine(s);
    

После определения запроса он может выполняться множество раз. И до выполнения запроса источник данных может изменяться. Чтобы более наглядно увидеть это, мы можем изменить какой-либо элемент до перебора выборки:

string[] people = { "Tom", "Sam", "Bob" };

var selectedPeople = people.Where(s=>s.Length == 3).OrderBy(s=>s);

people[2] = "Mike";
// выполнение LINQ-запроса
foreach (string s in selectedPeople)
    Console.WriteLine(s);

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

Важно понимать, что переменная запроса сама по себе не выполняет никаких действий и не возвращает никаких данных. Она только хранит набор команд, которые необходимы для получения результатов. То есть выполнение запроса после его создания откладывается. Само получение результатов производится при переборе в цикле foreach.

Немедленное выполнение запроса

С помощью ряда методов мы можем применить немедленное выполнение запроса. Это методы, которые возвращают одно атомарное значение или один элемент или данные типов Array, List и Dictionary. Полный список подобных операций в LINQ:

  • Aggregate

  • All

  • Any

  • Average

  • Contains

  • Count

  • ElementAt

  • ElementAtOrDefault

  • Empty

  • First

  • FirstOrDefault

  • Last

  • LastOrDefault

  • LongCount

  • Max

  • Min

  • SequenceEqual

  • Single

  • SingleOrDefault

  • Sum

  • ToArray

  • ToDictionary

  • ToList

  • ToLookup

Рассмотрим пример с методом Count(), который возвращает число элементов последовательности:

string[] people = { "Tom", "Sam", "Bob" };
// определение и выполнение LINQ-запроса
var count = people.Where(s=>s.Length == 3).OrderBy(s=>s).Count();

Console.WriteLine(count);   // 3 - до изменения коллекции

people[2] = "Mike";
Console.WriteLine(count);   // 3 - после изменения коллекции

Результатом метода Count будет объект int, поэтому сработает немедленное выполнение.

Сначала создается запрос: people.Where(s=>s.Length == 3).OrderBy(s=>s). Далее к нему применяется метод Count(), который выполняет запрос, неявно выполняет перебор по последовательности элементов, генерируемой этим запросом, и возвращает число элементов в этой последовательности.

Также мы можем изменить код таким образом, чтобы метод Count() учитывал изменения и выполнялся отдельно от определения запроса:

string[] people = { "Tom", "Sam", "Bob" };
// определение LINQ-запроса
var selectedPeople = people.Where(s=>s.Length == 3).OrderBy(s=>s);
// выполнение запроса
Console.WriteLine(selectedPeople.Count());   // 3 - до изменения коллекции

people[2] = "Mike";
// выполнение запроса
Console.WriteLine(selectedPeople.Count());   // 2 - после изменения коллекции

Также для немедленного выполнения LINQ-запроса и кэширования его результатов мы можем применять методы преобразования ToArray<T>(), ToList<T>(), ToDictionary() и т.д.. Эти методы получают результат запроса в виде объектов Array, List и Dictionary соответственно. Например:

string[] people = { "Tom", "Sam", "Bob" };

// определение и выполнение LINQ-запроса
var selectedPeople = people.Where(s=>s.Length == 3).OrderBy(s=>s).ToList();

// изменение массива никак не затронет список selectedPeople
people[2] = "Mike";

// выполнение запроса
foreach (string s in selectedPeople)
    Console.WriteLine(s);
Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850