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

C Sharp > LINQ Отложенное и немедленное выполнение LINQ
02.12.2016 12:20:47


Наиболее часто встречающиеся слова в статье:

[запроса] [selectedTeams] [выполнение] [Console] [WriteLine] [ToUpper] [StartsWith] [orderby] [LINQ-запроса] [foreach]


Статья:

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

При отложенном выполнении LINQ-выражение не выполняется, пока не будет произведена итерация или перебор по выборке. Рассмотрим отложенное выполнение:

string[] teams = {"Бавария", "Боруссия", "Реал Мадрид", "Манчестер Сити", "ПСЖ", "Барселона"};

var selectedTeams = from t in teams where t.ToUpper().StartsWith("Б") orderby t select t;

// выполнение LINQ-запроса
foreach (string s in selectedTeams)
Console.WriteLine(s);
То есть фактическое выполнение запроса происходит не в строке определения: var selectedTeams = from t..., а при переборе в цикле foreach.

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

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

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

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

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

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

string[] teams = {"Бавария", "Боруссия", "Реал Мадрид", "Манчестер Сити", "ПСЖ", "Барселона"};
Создание запроса - определение переменной selectedTeams:

var selectedTeams = from t in teams where t.ToUpper().StartsWith("Б") orderby t select t;
Выполнение запроса и получение его результатов:

foreach (string s in selectedTeams)
Console.WriteLine(s);
После определения запроса он может выполняться множество раз. И до выполнения запроса источник данных может изменяться. Чтобы более наглядно увидеть это, мы можем изменить какой-либо элемент до перебора выборки:


var selectedTeams = from t in teams where t.ToUpper().StartsWith("Б") orderby t select t;
// изменение массива после определения LINQ-запроса
teams[1] = "Ювентус";
// выполнение LINQ-запроса
foreach (string s in selectedTeams)
Console.WriteLine(s);
Теперь выборка будет содержать два элемента, а не три, так как второй элемент после изменения не будет соответствовать условию.

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

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

С помощью ряда методов мы можем применить немедленное выполнение запроса. Это методы, которые возвращают одно атомарное значение или один элемент. Например, Count(), Average(), First() / FirstOrDefault(), Min(), Max() и т.д. Например, метод Count() возвращает числовое значение, которое представляет количество элементов в полученной последовательности. А метод First() возвращает первый элемент последовательности. Но чтобы выполнить эти методы, вначале надо получить саму последовательность, то есть результат запроса, и пройтись по ней циклом foreach, который вызывается неявно внутри структуры запроса.

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


string[] teams = {"Бавария", "Боруссия", "Реал Мадрид", "Манчестер Сити", "ПСЖ", "Барселона"};
// определение и выполнение LINQ-запроса
int i = (from t in teams
where t.ToUpper().StartsWith("Б")
orderby t select t).Count();
Console.WriteLine(i); //3
teams[1] = "Ювентус";
Console.WriteLine(i); //3
Результатом метода Count будет объект int, поэтому сработает немедленное выполнение.

Сначала создается запрос: from t in teams where t.ToUpper().StartsWith("Б") orderby t select t. Далее к нему применяется метод Count(), который выполняет запрос, нявно выполняет перебор по последовательности элементов, генерируемой этим запросом, и возвращает число элементов в этой последовательности.

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


string[] teams = {"Бавария", "Боруссия", "Реал Мадрид", "Манчестер Сити", "ПСЖ", "Барселона"};
// определение LINQ-запроса
var selectedTeams = from t in teams
where t.ToUpper().StartsWith("Б")
orderby t select t;
// выполнение запроса
Console.WriteLine(selectedTeams.Count()); //3
teams[1] = "Ювентус";
// выполнение запроса
Console.WriteLine(selectedTeams.Count()); //2
Также для немедленного выполнения LINQ-запроса и кэширования его результатов мы можем применять методы преобразования ToArray<T>(), ToList<T>(), ToDictionary() и т.д.. Эти методы получают результат запроса в виде объектов Array, List и Dictionary соответственно. Например:


string[] teams = {"Бавария", "Боруссия", "Реал Мадрид", "Манчестер Сити", "ПСЖ", "Барселона"};
// выполнение LINQ-запроса
var selectedTeams = (from t in teams
where t.ToUpper().StartsWith("Б")
orderby t select t).ToList<string>();
// изменение массива никак не затронет список selectedTeams
teams[1] = "Ювентус";

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