Краткое описание основных методов LINQ простым языком, без терминов "проекция", "предикат", "делегат" и проч. От термина "лямбда-выражение" избавиться не удалось :)

Что такое LINQ

LINQ (Language Integrated Query) – это набор методов для работы с коллекциями.

Для простоты здесь будем называть коллекцией любой объект, который хранит несколько других объектов. Это может быть массив из чисел, или результат запроса к базе данных, или набор элементов, прочитанных из документа XML и т.д.

Немного занудства

Правильнее было бы использовать термин «последовательность». Для LINQ был разработан набор классов, реализующих различные виды последовательностей. Последовательность в LINQ - это класс, который реализует интерфейс IEnumerable или IQueryable, и позволяет методам LINQ работать с разными видами данных.

Коллекции в .NET – это набор классов, предназначенный для работы с различными списками объектов (массивы, словари, очереди, стеки и др.).

Чаще всего методы LINQ используются для работы с коллекциями. Поэтому далее будет использоваться термин «коллекция», как более лаконичный и интуитивно-понятный.

Методы LINQ вызываются в исходном коде, как и другие методы, пример на языке C#:

var result = nums.Select( n => n*2 );

Что такое лямбда-выражение

В предыдущем примере вызывается метод Select, за ним текст в скобках – это лямбда-выражение, краткая форма описания функции. Перед => указывается список входных параметров, в данном примере входной параметр один, он обозначается n. После => указывается программный код, который использует входные параметры и возвращает рассчитанное значение. В данном примере функция возвращает значение входного параметра, умноженное на 2.

Цепочки вызовов методов LINQ

Для большинства методов LINQ результатом является тоже коллекция, к которой можно применить другой метод LINQ, например:

var result = nums.Select( n => n*2 ).Where( n => n > 4 ).OrderBy( n => n );

Так можно «цеплять» методы LINQ один за другим много раз.

На самом деле всё сложнее

В примере выше в переменную result заносится результат последовательного выполнения методов Select, Where, OrderBy – но это не означает, что в переменной result теперь хранятся элементы коллекции nums, которые сначала были умножены на 2, затем из них были отобраны те, которые больше 4, а потом отсортированы по возрастанию.

В переменной result хранится ссылка на методы LINQ, которые надо будет вызвать, порядок вызова этих методов и лямбда-выражения, используемые в этих методах.

Можно сказать, что переменная result – это коробка с перфокартами, на которых записан программный код.

Допустим, что для переменной result вызвали еще один метод Select, и результат занесен в переменную result2:

var result2 = result.Select( n => n+1 );

Это означает, что все перфокарты из коробки result переложили в коробку result2, а в конец еще добавили перфокарты с описанием вызова Select( n => n+1 ).

Когда же перфокарты будут выполнены? Это происходит при вызове метода LINQ, который должен вернуть определенное значение или список значений.

Например, метод Count возвращает число элементов в коллекции:

var cnt = result2.Count();

В данном примере при вызове метода Count все элементы коллекции nums будут умножены на 2, затем из них отберутся те, которые больше 4, они будут отсортированы по возрастанию, затем к каждому полученному элементу прибавится 1, и после этого метод Count посчитает количество полученных элементов, которое и занесется в переменную cnt.

Такой механизм в LINQ называется «отложенное выполнение».

Синтаксис метода и синтаксис запроса

В предыдущем примере методы LINQ вызываются, как любые другие методы в языке C#. Такой синтаксис называется синтаксис метода (Method Syntax).

Вызовы методов LINQ можно описать в более простом виде, используя синтаксис запроса (Query Syntax), например:

var result = from n in nums
                   where n*2 > 4
                   orderby n
                   select n*2;

Вызов метода LINQ в синтаксисе запроса всегда начинается с конструкции from и заканчивается конструкцией либо select, либо group.

Синтаксис запроса LINQ похож на SQL, но это не SQL, это другой синтаксис, и не надо искать соответствия.

Далее - краткое описание основных методов LINQ.

Select  - Цикл по коллекции

Описание коллекции для примера:

// создать коллекцию из 4-х чисел
int[] nums = { 1, 2, 3, 4 }; 

Синтаксис запроса (Query Syntax)

Пример в синтаксисе запроса:

var result1 = from n in nums select n+1;

Разбор примера по частям:

from n in nums
соответствует
Для каждого элемента n в исходной коллекции nums

select n+1
соответствует
добавить в результирующую коллекцию значение, полученное из выражения n+1

Итоговый разбор примера:

var result1 = from n in nums select n+1

соответствует
Для каждого элемента n в исходной коллекции nums
добавить в результирующую коллекцию значение, полученное из выражения n+1.
Поместить результирующую коллекцию в переменную result1.

Синтаксис метода (Method Syntax)

Предыдущий пример в синтаксисе метода:

var result2 = nums.Select( n => n+1 );

В методе Select в скобках описывается функция, которая возвращает элемент результирующей коллекции.

Входной параметр функции – после открывающей скобки и перед =>. В данном примере входной параметр функции - переменная n.

Во входном параметре функции передается элемент исходной коллекции.

Разбор примера по частям:

nums.Select
соответствует
Для каждого элемента в исходной коллекции nums

( n
соответствует
, используя переменную n для обозначения элемента коллекции nums,

=> n+1
соответствует
добавить в результирующую коллекцию значение, полученное из выражения n+1

Итоговый разбор примера:

var result2 = nums.Select( n => n+1 );

соответствует
Для каждого элемента в исходной коллекции nums
, используя переменную n для обозначения элемента коллекции nums,
добавить в результирующую коллекцию значение, полученное из выражения n+1.
Поместить результирующую коллекцию в переменную result2.

В обоих вариантах (синтаксис запроса и синтаксис метода) результирующая коллекция содержит числа: 2, 3, 4, 5

SelectMany  - Вложенный цикл (декартово произведение)

Описание коллекций для примера:

int[] nums = { 1, 2, 3, 4 }; // создать коллекцию из 4-х чисел
string[] chars = { "a", "b" }; // создать коллекцию из 2-х строк

Синтаксис запроса (Query Syntax)

Пример в синтаксисе запроса:

var result3 = from n in nums
              from c in chars
              select c + n.ToString();

Разбор примера по частям:

from n in nums
from c in chars
соответствует
Для каждого элемента n в исходной коллекции nums
и для каждого элемента c в исходной коллекции chars

select c + n.ToString()
соответствует
добавить в результирующую коллекцию значение, полученное из выражения c + n.ToString()

Итоговый разбор примера:

var result3 = from n in nums
              from c in chars
              select c + n.ToString();

соответствует
Для каждого элемента n в исходной коллекции nums
и для каждого элемента c в исходной коллекции chars
добавить в результирующую коллекцию значение, полученное из выражения c + n.ToString().
Поместить результирующую коллекцию в переменную result3.

Синтаксис метода (Method Syntax)

Предыдущий пример в синтаксисе метода:

var result4 = nums.SelectMany( n => chars.Select( c => c + n.ToString() ));

В методе SelectMany в параметре (т.е. в скобках) описывается функция, которая возвращает коллекцию.
Результирующая коллекция образуется путём объединения всех коллекций, полученных при вызове функции, описанной в скобках.
Входной параметр функции – после открывающей скобки и перед =>. В данном примере входной параметр функции - переменная n.
Во входном параметре функции передается элемент исходной коллекции. В данном примере исходная коллекция - nums, а в переменной n передаётся элемент коллекции nums.

Разбор примера по частям:

nums.SelectMany
соответствует
Для каждого элемента в исходной коллекции nums

( n
соответствует
, используя переменную n для обозначения элемента коллекции nums,

=>
соответствует
добавить в результирующую коллекцию объекты коллекции, полученной по следующему алгоритму:

chars.Select
соответствует
Для каждого элемента в исходной коллекции chars

( c
соответствует
, используя переменную c для обозначения элемента коллекции chars,

=> c + n.ToString()
соответствует
добавить в результирующую коллекцию значение, полученное из выражения c + n.ToString()

Итоговый разбор примера:

var result4 = nums.SelectMany( n => chars.Select( c => c + n.ToString() ));

соответствует
Для каждого элемента в исходной коллекции nums
, используя переменную n для обозначения элемента коллекции nums,
добавить в результирующую коллекцию объекты коллекции, полученной по следующему алгоритму:
Для каждого элемента в исходной коллекции chars
, используя переменную c для обозначения элемента коллекции chars,
добавить в результирующую коллекцию значение, полученное из выражения c + n.ToString().
Поместить результирующую коллекцию в переменную result4.

В обоих вариантах (синтаксис запроса и синтаксис метода) результирующая коллекция содержит строки: a1, b1, a2, b2, a3, b3, a4, b4

Еще один пример метода SelectMany

Описание коллекции для примера:

/* создать коллекцию из 2 объектов, 
   где каждый объект описывает категорию товаров 
   и содержит поля Name (название категории) и Goods (список названий товаров) */

var categories = new[] { new { Name = "Flowers", 
                               Goods = new string[] { "Rose", "Astra", "Tulip" } },
                         new { Name = "Candies", 
                               Goods = new string[] { "Bonbon", "Chocolate" } } };

Пример в синтаксисе запроса:

var goods1 = from c in categories
             from g in c.Goods
             select g;

Предыдущий пример в синтаксисе метода:

var goods2 = categories.SelectMany(c => c.Goods );

В обоих вариантах (синтаксис запроса и синтаксис метода) результирующая коллекция содержит общий список названий товаров: Rose, Astra, Tulip, Bonbon, Chocolate

Where  - Поиск элементов в коллекции по условиям поиска

Описание коллекции для примера:

// создать коллекцию из 3-х строк
string[] names = { "Tom", "Bob", "Nick" }; 

Синтаксис запроса (Query Syntax)

Пример в синтаксисе запроса:

var foundNames1 = from n in names
                 where n.Contains("o")
                 select n;

Разбор примера по частям:

from n in names
соответствует
Для каждого элемента n в исходной коллекции names

where n.Contains("o")
соответствует
проверить условие n.Contains("o") (значение n содержит букву o).

select n;
соответствует
Если условие выполнилось, добавить в результирующую коллекцию значение n.

Итоговый разбор примера:

var foundNames1 = from n in names
                 where n.Contains("o")
                 select n;

соответствует
Для каждого элемента n в исходной коллекции names
проверить условие n.Contains("o") (значение n содержит букву o).
Если условие выполнилось, добавить в результирующую коллекцию значение n.
Поместить результирующую коллекцию в переменную foundNames1.

Синтаксис метода (Method Syntax)

Предыдущий пример в синтаксисе метода:

var foundNames2 = names.Where( n => n.Contains("o") );

В методе Where в скобках описывается функция, которая возвращает true или false («Истина» или «Ложь»).
Входной параметр функции – после открывающей скобки и перед =>. В данном примере входной параметр функции - переменная n.
Во входном параметре функции передается элемент исходной коллекции.
Метод Where возвращает коллекцию элементов, для которых функция в скобках вернула true.

Разбор примера по частям:

names.Where
соответствует
Для каждого элемента в исходной коллекции names

( n
соответствует
, используя переменную n для обозначения элемента коллекции names,

=> n.Contains("o")
соответствует
проверить условие n.Contains("o") (значение n содержит букву o).
Если условие выполнилось, добавить в результирующую коллекцию значение n.

Итоговый разбор примера:

var foundNames2 = names.Where( n => n.Contains("o") );

соответствует
Для каждого элемента в исходной коллекции names
, используя переменную n для обозначения элемента коллекции names,
проверить условие n.Contains("o") (значение n содержит букву o).
Если условие выполнилось, добавить в результирующую коллекцию значение n.
Поместить результирующую коллекцию в переменную foundNames2.

В обоих вариантах (синтаксис запроса и синтаксис метода) результирующая коллекция содержит строки: Tom, Bob

Single  - Поиск одного элемента в коллекции

Описание коллекции для примера:

int[] nums = { 1, 2, 3, 4 }; // создать коллекцию из 4-х чисел

Синтаксис запроса (Query Syntax)

В синтаксисе запроса нет операторов по получению единственного элемента из коллекции.

Синтаксис метода (Method Syntax)

Пример в синтаксисе метода:

var result5 = nums.Single( n => n == 3 );

В методе Single в скобках описывается функция, которая возвращает true или false («Истина» или «Ложь»).
Входной параметр функции – после открывающей скобки и перед =>. В данном примере входной параметр функции - переменная n.
Во входном параметре функции передается элемент исходной коллекции.
Метод Single возвращает элемент коллекции, для которого функция в скобках вернула true. Если таких элементов 0 или больше 1, возникнет исключение.

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

Разбор примера по частям:

nums.Single
соответствует
Для каждого элемента в исходной коллекции nums

( n
соответствует
, используя переменную n для обозначения элемента коллекции nums,

=> n == 3  
соответствует
проверить условие n == 3 (значение n равно 3)

Итоговый разбор примера:

var result5 = nums.Single( n => n == 3 );

соответствует
Для каждого элемента в исходной коллекции nums
, используя переменную n для обозначения элемента коллекции nums,
проверить условие n == 3 (значение n равно 3).
Если условие выполнилось для единственного элемента, поместить значение этого элемента в переменную result5.

В данном примере переменная result5 содержит число: 3

Join – Из двух исходных коллекций скомпоновать одну коллекцию, найдя соответствия между элементами исходных коллекций

Описание коллекций для примера:

/* создать коллекцию из 3-х объектов, 
   где каждый объект описывает студента 
   и содержит поля Name (имя студента) и FacultyId (код факультета) */

var students = new[] { new { Name = "Tom", FacultyId = 1 }, 
                     new { Name = "Bob", FacultyId = 2 }, 
                     new { Name = "John", FacultyId = 2 } };  

/* создать коллекцию из 2-х объектов, 
   где каждый объект описывает факультет 
   и содержит поля Name (название факультета) и Id (код факультета) */

var faculties = new[] { new { Name = "Faculty of Mathematics", Id = 1 }, 
                       new { Name = "Faculty of Physics",     Id = 2 } };  

Синтаксис запроса (Query Syntax)

Пример в синтаксисе запроса:

var result10 = from s in students
               join f in faculties
               on s.FacultyId equals f.Id
               select new { Name = s.Name, Faculty = f.Name };

Разбор примера по частям:

from s in students
join f in faculties
соответствует
Для каждого объекта s в исходной коллекции students
и для каждого объекта f в исходной коллекции faculties

on s.FacultyId equals f.Id
соответствует
проверить условие - значение поля FacultyId (код факультета) в объекте коллекции students равно значению поля Id (код факультета) в объекте коллекции faculties.

select new { Name = s.Name, Faculty = f.Name };
соответствует
Если условие сравнения выполняется, добавить в результирующую коллекцию объект, где поле Name содержит имя студента из поля Name в объекте коллекции students, а поле Faculty содержит название факультета из поля Name в объекте коллекции faculties.

Итоговый разбор примера:

var result10 = from s in students
               join f in faculties
               on s.FacultyId equals f.Id
               select new { Name = s.Name, Faculty = f.Name };

соответствует
Для каждого объекта s в исходной коллекции students
и для каждого объекта f в исходной коллекции faculties
проверить условие - значение поля FacultyId (код факультета) в объекте коллекции students
равно значению поля Id (код факультета) в объекте коллекции faculties.
Если условие сравнения выполняется, добавить в результирующую коллекцию объект, где поле Name содержит имя студента из поля Name в объекте коллекции students, а поле Faculty содержит название факультета из поля Name в объекте коллекции faculties.
Поместить результирующую коллекцию в переменную result10.

Синтаксис метода (Method Syntax)

Предыдущий пример в синтаксисе метода:

var result11 = students.Join(faculties, 
                             s => s.FacultyId, 
                             f => f.Id, 
                             (s, f) => new 
                             { 
                               Name = s.Name, 
                               Faculty = f.Name 
                             });

В методе Join в первый параметр передаётся коллекция, где будут искаться соответствующие элементы для каждого элемента исходной коллекции.
В данном примере в первый параметр передаётся коллекция faculties.
Т.о. для каждого объекта коллекции students будут искаться соответствующие объекты в коллекции faculties.

Второй параметр метода Join описывает функцию, которая возвращает значение, полученное из объекта первой коллекции, используемое для сравнения с объектом из второй коллекции.
В данном примере второй параметр метода Join описывает функцию:
s => s.FacultyId
Эта функция возвращает значение поля FacultyId (код факультета) из объекта коллекции students.

Третий параметр метода Join описывает функцию, которая возвращает значение, полученное из объекта второй коллекции, используемое для сравнения с объектом из первой коллекции.
В данном примере третий параметр метода Join описывает функцию:
f => f.Id
Эта функция возвращает значение поля Id (код факультета) из объекта коллекции faculties.
Т.о. при сравнении объекта студента и объекта факультета будет сравниваться код факультета.

Четвертый параметр метода Join описывает функцию, которая на вход получает объект первой коллекции и объект второй коллекции, для которых выполнилось условие сравнения.
Функция возвращает объект, который создаётся для данных двух объектов. Этот объект будет добавлен в результирующую коллекцию.
В данном примере четвертый параметр метода Join описывает функцию:
(s, f) => new { Name = s.Name, Faculty = f.Name }
Т.о. результирующая коллекция будет содержать объекты, в которых поле Name содержит имя студента, а поле Faculty содержит название факультета.

Разбор примера по частям:

students.Join(faculties
соответствует
Для каждого объекта в коллекции students
и для каждого объекта в коллекции faculties

s => s.FacultyId,
f => f.Id,
соответствует
сравнить значение поля FacultyId в объекте первой коллекции и значение поля Id в объекте второй коллекции.

(s, f) => new { Name = s.Name, Faculty = f.Name }
соответствует
Если условие сравнения выполняется, добавить в результирующую коллекцию объект, где поле Name содержит значение поля Name в объекте первой коллекции, а поле Faculty содержит значение поля Name в объекте второй коллекции.

Итоговый разбор примера:

var result11 = students.Join(faculties, 
                             s => s.FacultyId, 
                             f => f.Id, 
                             (s, f) => new { Name = s.Name, Faculty = f.Name });

соответствует
Для каждого объекта в коллекции students
и для каждого объекта в коллекции faculties
сравнить значение поля FacultyId в объекте первой коллекции и значение поля Id в объекте второй коллекции.
Если условие сравнения выполняется, добавить в результирующую коллекцию объект, где поле Name содержит значение поля Name в объекте первой коллекции, а поле Faculty содержит значение поля Name в объекте второй коллекции.
Поместить результирующую коллекцию в переменную result11.

В обоих вариантах (синтаксис запроса и синтаксис метода) результирующая коллекция содержит 3 объекта, где каждый объект описывает студента и содержит поля Name (имя студента) и Faculty (название факультета):

Name

Faculty

Tom

Faculty of Mathematics

Bob

Faculty of Physics

John

Faculty of Physics

GroupJoin – Из коллекции «мастер-таблица» и коллекции «дочерняя таблица» скомпоновать одну иерархическую коллекцию

Описание коллекций для примера:

/* создать коллекцию из 3-х объектов, 
   где каждый объект описывает отдел 
   и содержит поля Name (название отдела) и Id (код отдела) */

var departments = new[] { new { Name = "Purchasing Department", Id = 1 },
                     new { Name = "Sales Department", Id = 2 },
                     new { Name = "Analytics Department", Id = 3 } };

/* создать коллекцию из 4-х объектов, 
   где каждый объект описывает сотрудника 
   и содержит поля Name (имя сотрудника) и DepId (код отдела) */

var workers = new[] { new { Name = "Harry",     DepId = 1 },
                       new { Name = "George",   DepId = 1 },
                       new { Name = "Jessica",  DepId = 1 },
                       new { Name = "Emma",     DepId = 2 } };

Синтаксис запроса (Query Syntax)

Пример в синтаксисе запроса:

var result12 = from d in departments
               join w in workers
               on d.Id equals w.DepId
               into foundWorkers
               select new 
               { 
                   DepName    = d.Name, 
                   DepWorkers = foundWorkers 
               };

Разбор примера по частям:

from d in departments
соответствует
Для каждого объекта d из коллекции departments

join w in workers
on d.Id equals w.DepId
соответствует
найти объекты в коллекции workers по условию:
значение поля Id (код отдела) в объекте из коллекции departments равно значению поля DepId (код отдела) в объекте из коллекции workers.

into foundWorkers
соответствует
Для каждого объекта из коллекции departments сохранить результаты поиска в переменной foundWorkers.

select new
{
  DepName    = d.Name,
  DepWorkers = foundWorkers
};
соответствует
Для каждого объекта из коллекции departments создать объект, в котором поле DepName содержит название отдела, а поле DepWorkers содержит коллекцию объектов из коллекции workers, найденных для данного отдела.

Итоговый разбор примера:

var result12 = from d in departments
               join w in workers
               on d.Id equals w.DepId
               into foundWorkers
               select new 
               { 
                   DepName    = d.Name, 
                   DepWorkers = foundWorkers 
               };

соответствует
Для каждого объекта d из коллекции departments
найти объекты в коллекции workers по условию:
значение поля Id (код отдела) в объекте из коллекции departments равно значению поля Id (код отдела) в объекте из коллекции workers.
Для каждого объекта из коллекции departments сохранить результаты поиска в переменной foundWorkers.
Для каждого объекта из коллекции departments создать объект, в котором поле DepName содержит название отдела, а поле DepWorkers содержит коллекцию объектов из коллекции workers, найденных для данного отдела.
Поместить результирующую коллекцию в переменную result12.

В результирующую коллекцию попадут и те объекты из коллекции departments, для которых не нашлись объекты из коллекции workers.

Синтаксис метода (Method Syntax)

Предыдущий пример в синтаксисе метода:

var result13 = departments.GroupJoin(workers,
                                      d => d.Id,
                                      w => w.DepId,
                                      (d, foundWorkers) => new 
                                      { 
                                          DepName    = d.Name, 
                                          DepWorkers = foundWorkers 
                                      });

В методе GroupJoin в первый параметр передаётся коллекция, где будут искаться соответствующие элементы для каждого элемента исходной коллекции.

Второй параметр метода GroupJoin описывает функцию, которая возвращает значение, полученное из объекта первой коллекции, используемое для сравнения с объектом из второй коллекции.
В данном примере второй параметр метода GroupJoin описывает функцию:
d => d.Id
Эта функция возвращает значение поля Id (код отдела) из объекта коллекции departments.

Третий параметр метода GroupJoin описывает функцию, которая возвращает значение, полученное из объекта второй коллекции, используемое для сравнения с объектом из первой коллекции.
В данном примере третий параметр метода GroupJoin описывает функцию:
w => w.DepId
Эта функция возвращает значение поля DepId (код отдела) из объекта второй коллекции workers.
Т.о. при сравнении объекта отдела и объекта работника будет сравниваться код отдела.

Четвертый параметр метода GroupJoin описывает функцию, которая на вход получает объект первой коллекции и коллекцию объектов второй коллекции, найденных для данного объекта первой коллекции.
Функция возвращает объект, который будет добавлен в результирующую коллекцию.

В данном примере четвертый параметр метода GroupJoin описывает функцию:
(d, foundWorkers) => new
{
    DepName    = d.Name,
    DepWorkers = foundWorkers
});

Т.о. результирующая коллекция будет содержать объекты, в которых поле DepName содержит название отдела, а поле DepWorkers содержит коллекцию объектов из коллекции workers, найденных для данного отдела.

Разбор примера по частям:

departments.GroupJoin(workers
соответствует
Для каждого объекта в исходной коллекции departments
найти объекты в коллекции workers

d => d.Id,
w => w.DepId,
соответствует
по условию:
значение поля Id (код отдела) в объекте из исходной коллекции departments равно значению поля DepId (код отдела) в объекте из коллекции workers.

(d, foundWorkers) => new
{
    DepName    = d.Name,
    DepWorkers = foundWorkers
});
соответствует
Для каждого объекта из исходной коллекции departments создать объект, в котором поле DepName содержит название отдела, а поле DepWorkers содержит коллекцию объектов из коллекции workers, найденных для данного отдела.

Итоговый разбор примера:

var result13 = departments.GroupJoin(workers,
                                      d => d.Id,
                                      w => w.DepId,
                                      (d, foundWorkers) => new 
                                      { 
                                          DepName    = d.Name, 
                                          DepWorkers = foundWorkers 
                                      });

соответствует
Для каждого объекта в исходной коллекции departments
найти объекты в коллекции workers
по условию:
значение поля Id (код отдела) в объекте из исходной коллекции departments равно значению поля DepId (код отдела) в объекте из коллекции workers.
Для каждого объекта из исходной коллекции departments создать объект, в котором поле DepName содержит название отдела, а поле DepWorkers содержит коллекцию объектов из коллекции workers, найденных для данного отдела.
Поместить результирующую коллекцию в переменную result13.

В обоих вариантах (синтаксис запроса и синтаксис метода) результирующая коллекция содержит 3 объекта (соответствующих 3 объектам исходной коллекции departments), где каждый объект описывает отдел и содержит поле DepName (название отдела) и поле DepWorkers (коллекция объектов сотрудников отдела):

DepName

DepWorkers

Name

DepId

Purchasing Department

Harry

1

George

1

Jessica

1

Sales Department

Emma

2

Analytics Department

 

 

GroupBy - Разбить коллекцию на несколько коллекций, каждая из которых содержит элементы с одинаковыми значениями ключевых полей

Описание коллекции для примера:

/* создать коллекцию из 3-х объектов, 
   где каждый объект описывает студента и содержит поля Name и Age */
var people = new[] { new { Name = "Tom", Age = 24 }, 
                     new { Name = "Bob", Age = 25 }, 
                     new { Name = "John", Age = 25 } };  

Синтаксис запроса (Query Syntax)

Пример в синтаксисе запроса:

var ageGroups1 = from p in people
                 group p.Name by p.Age;

Разбор примера по частям:

from p in people
соответствует
Из исходной коллекции people
, используя переменную p для обозначения объекта коллекции people,

group p.Name
соответствует
отобрать группы объектов, где каждый объект является значением p.Name

by p.Age
соответствует
, при этом в одну группу объектов попадают значения, полученные из объектов исходной коллекции с одинаковым значением поля Age

Итоговый разбор примера:

var ageGroups1 = from p in people
                 group p.Name by p.Age;

соответствует
Из исходной коллекции people,
, используя переменную p для обозначения объекта коллекции people,
отобрать группы объектов, где каждый объект является значением p.Name
, при этом в одну группу объектов попадают значения, полученные из объектов исходной коллекции с одинаковым значением поля Age.
Поместить результирующую коллекцию групп объектов в переменную ageGroups1.

Синтаксис метода (Method Syntax)

Предыдущий пример в синтаксисе метода:

var ageGroups2 = people.GroupBy( p => p.Age, p => p.Name );

В методе GroupBy может быть от одного до трёх параметров

Первый параметр метода GroupBy описывает функцию, которая определяет ключ для группировки.
В данном примере первый параметр метода GroupBy описывает функцию:
p => p.Age
Эта функция возвращает значение поля Age из объекта коллекции people.
Т.о. в данном примере будут созданы группы объектов, соответствующих объектам коллекции people с одинаковыми значениями поля Age.

Второй параметр метода GroupBy описывает функцию, которая возвращает объект, который будет добавлен в результирующую группу элементов.
Этот параметр необязательный.
В данном примере второй параметр метода GroupBy описывает функцию:
p => p.Name
Т.о. в данном примере будут созданы группы объектов, где каждый объект содержит значение поля Name из объекта коллекции people.

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

Третий параметр метода GroupBy описывает функцию, которая сравнивает ключевые значения, полученные из функции, описанной в первом параметре метода GroupBy. Этот параметр необязательный.
Если в методе GroupBy отсутствует третий параметр, тогда ключевые значения сравниваются встроенным механизмом.

Разбор примера по частям:

people.GroupBy
соответствует
Из исходной коллекции people отобрать группы объектов

( p => p.Age
соответствует
, соответствующих объектам коллекции people с одинаковым значением поля Age.

, p => p.Name
соответствует
В результирующих группах объектов каждый объект содержит значение поля Name из объекта коллекции people.

Итоговый разбор примера:

var ageGroups2 = people.GroupBy( p => p.Age, p => p.Name );
соответствует
Из исходной коллекции people отобрать группы объектов
, соответствующих объектам коллекции people с одинаковым значением поля Age.
В результирующих группах объектов каждый объект содержит значение поля Name из объекта коллекции people.
Поместить результирующую коллекцию групп объектов в переменную ageGroups2.

В обоих вариантах (синтаксис запроса и синтаксис метода) результирующая коллекция групп объектов содержит 2 группы объектов:
Ключевое поле: 24, объекты в группе: Tom
Ключевое поле: 25, объекты в группе: Bob, John

Краткое описание других методов LINQ

Методы сортировки

OrderBy – сортировка по возрастанию
OrderByDescending - сортировка по убыванию
ThenBy - сортировка по возрастанию, с учетом предыдущей сортировки
ThenByDescending - сортировка по убыванию, с учетом предыдущей сортировки

Методы расчета агрегата (например, суммы значений всех элементов коллекции).

Sum - сумма значений всех элементов коллекции
Average - среднее значение всех элементов коллекции
Min – минимальное значение элемента коллекции
Max - максимальное значение элемента коллекции
Count – количество элементов в коллекции
Aggregate – результат операции, выполненной последовательно над всеми элементами коллекции (например, умножить все элементы друг на друга)

Методы проверки условий на элементах коллекции

 All – вернуть true, если условие выполняется для всех элементов коллекции
Any - вернуть true, если условие выполняется хотя бы для одного элемента коллекции
Contains - вернуть true, если коллекция содержит указанный элемент

Методы объединения и пересечения коллекций

Concat – объединить две коллекции, все элементы двух коллекций попадут в результирующую коллекцию
Except – вернуть коллекцию, содержащие элементы, которые присутствуют в первой коллекции и отсутствуют во второй коллекции
Intersect - вернуть коллекцию, содержащие элементы, которые одновременно присутствуют и в первой, и во второй коллекции
Union - объединить две коллекции, исключая дублирование

Методы пропуска элементов (аналоги пагинации)

Skip – вернуть коллекцию, где отсутствует заданное количество первых элементов
SkipLast - вернуть коллекцию, где отсутствует заданное количество последних элементы
SkipWhile – исключать из исходной коллекции элементы, начиная с первого, до тех пор, пока выполняется заданное условие. Вернуть коллекцию, содержащую все остальные элементы, начиная с первого, для которого не выполнилось условие.
Take - вернуть коллекцию, содержащую заданное количество первых элементов
TakeLast - вернуть коллекцию, содержащую заданное количество последних элементов
TakeWhile – вернуть коллекцию, включающую элементы из исходной коллекции, начиная с первого, до тех пор, пока выполняется заданное условие

Прочие методы

Cast – привести все элементы коллекции к указанному типу
Distinct - удалить повторяющиеся значения из коллекции
ToArray, ToDictionary, ToList – преобразовать коллекцию в коллекцию другого типа
ToLookup – вернуть коллекцию, содержащую пары «ключ-значение», представляющую собой словарь «один ко многим», аналог индекса в базе данных

Комментарии (15)


  1. E1ektr0
    17.08.2024 21:46
    +4

    Извините за духоту, но на самом деле, лучше выучить термин делегат, ознакомиться с тем что такое предикат, и использовать документацию Майкрософт. По крайней мере, от .net разработчика ожидаешь этого.

    https://learn.microsoft.com/en-us/dotnet/csharp/linq/get-started/introduction-to-linq-queries


  1. nronnie
    17.08.2024 21:46

    и использовать документацию Майкрософт

    Разработчику не нужно уметь читать.


  1. onets
    17.08.2024 21:46
    +3

    Объемный труд.

    А я помню времена, когда он только появился. Появилась возможность мыслить множествами, как в SQL. Некоторые задачи лучше решаются таким способом.


  1. Cryvage
    17.08.2024 21:46
    +11

    И ни слова про ленивость. Как человек должен писать код, не понимая как этот код выполняется? Результатом работы методов LINQ является не коллекция, а IEnumerable, за которым скрывается метод - генератор коллекции. Не понимая этого, можно такого наворотить...


    1. alex1303 Автор
      17.08.2024 21:46

      Спасибо за полезное замечание. Добавил в текст описание отложенного выполнения. Статья предназначена для первоначального знакомства с LINQ, поэтому старался всё упростить по максимуму.


  1. OmegaRat
    17.08.2024 21:46
    +3

    var result4 = nums.SelectMany( n => chars.Select( c => c + n.ToString() ));

    Не совсем корректный пример для метода SelectMany. Получается просто вложенный Select, а SelectMany делает из вложенных последовательностей одну единую. Правильнее и нагляднее было бы сделать вот так:

    var result4 = nums.SelectMany(n => chars, (n, c) => c + n.ToString());

    Также очень хотелось бы увидеть примеры с методом Aggregate, ибо без него просто никуда. Банально тот же BigInteger не имеет других методов агрегации, кроме него. Да и вообще с ним можно много всего наворотить. )

    Очень хорошо Linq разобран в книге Джозефа Албахари "C# 9.0. Справочник. Полное описание языка" (Joseph Albahari "C# 9.0 IN A NUTSHELL. THE DEFINITIVE REFERENCE").


    1. alex1303 Автор
      17.08.2024 21:46

      Спасибо за полезное замечание. Для SelectMany в статье два примера. Первый пример иллюстрирует реализацию вложенного цикла, а второй - склеивание коллекций.

      Метод Aggregate упомянут в конце статьи, без примеров, т.к. статья предназначена для первоначального знакомства, это не полноценный учебник по LINQ.

      У Албахари я читал "LINQ. Карманный справочник", там хорошо все разжевано.


    1. VanKrock
      17.08.2024 21:46

      Очень спорное утверждение, вот я за более 10 лет программирования на C# использовал метод Aggregate может раза 2 от силы. Вместо Aggregate обычно проходитесь по перечислению через foreach и днлаете любые преобразования и аггрегируете как хотите


  1. YuryZakharov
    17.08.2024 21:46
    +3

    Мне кажется, Вы неправильно описываете парадигму LINQ.

    Вот, например:

    var result2 = nums.Select( n => n+1 );

    Для каждого элемента в исходной коллекции nums
    , используя переменную n для обозначения элемента коллекции nums,
    добавить в результирующую коллекцию значение, полученное из выражения n+1.
    Поместить результирующую коллекцию в переменную result2.

    В том-то и дело, что в result2 ничего не помещается на данном этапе.

    Тут описание, скорее, должно быть

    result2 - это результат применения указанной функции к элементам коллекции nums.

    Всё. Как именно LINQ перебирает элементы исходной коллекции, вообще не важно. Это детали реализации, которые, в теории, могут быть любые.

    То же самое и с фильтрами - не нужно помещать или не помещать элемент в итоговую коллекцию. Результат работы Where это коллекция, содержащая только те элементы исходной, которые соответствуют предикату (проходят фильтр).

    LINQ - он именно об этом. Декларативное описание.

    Императивное "взять каждый элемент и что-то с ним сделать, поместить результат куда-то там" - это всё появляется (или не появляется) только когда вы материализуете результат каким-нибудь .ToArray() или foreach.

    Погрузитесь немного глубже в тему, это не сложно, это интересно.


    1. alex1303 Автор
      17.08.2024 21:46

      Спасибо за полезное замечание. Добавил в текст описание отложенного выполнения. Статья предназначена для первоначального знакомства с LINQ, поэтому старался всё упростить по максимуму.


      1. YuryZakharov
        17.08.2024 21:46

        Спорно. Для новичков тем более нужно правильное упрощение. А у вас императивщина, что в корне не верно в случае LINQ.


        1. alex1303 Автор
          17.08.2024 21:46

          Согласен, что надо обязательно обратить внимание на декларативность операторов LINQ. Поэтому я добавил спойлер "На самом деле всё сложнее" с описанием отложенного выполнения.


          1. YuryZakharov
            17.08.2024 21:46
            +1

            Будь я новичком, тут же достиг бы просветления.


  1. Naf2000
    17.08.2024 21:46

    IQueryable это вообще отдельная история. Другие типы, другой принцип работы. Просто общий внешний вид


  1. Proscrito
    17.08.2024 21:46

    Статьи о том как использовать, обычно неполны без пояснений: зачем использовать. То есть о плюсах и минусах.

    И было бы не лишним упомянуть про альтернативные реализации linq, типа SimdLinq. И снова: зачем нужны и чем заплатим.