Соединение коллекций

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

Соединение в LINQ используется для объединения двух разнотипных наборов в один. Для соединения используется оператор join или метод Join(). Как правило, данная операция применяется к двум наборам, которые имеют один общий критерий.

Оператор join

Оператор join имеет следующий формальный синтаксис:

from объект1 in набор1
join объект2 in набор2 on объект2.свойство2 equals объект1.свойство1

После оператора join идет выборка объектов из второй коллекции. После оператора on указывается критерий соединения - свойство объекта из второй выборки, а после оператора equals - свойство объекта из первой выборки, которому должно быть равно свойство объекта из второй выборки. Если эти свойства равны, то оба объекта попадают в финальный результат.

Например, у нас есть два класса:

record class Person(string Name, string Company);
record class Company(string Title, string Language);

Класс Person представляет пользователя и хранит два свойства: Name (имя) и Company (компания пользователя). Класс Company представляет компанию и хранит два свойства: Title (название компании) и Language (основной язык программирования в компании)

Объекты обоих классов будет иметь один общий критерий - название компании. Соединим по этому критерию два набора этих классов:

Person[] people =
{
    new Person("Tom", "Microsoft"), new Person("Sam", "Google"),
    new Person("Bob", "JetBrains"), new Person("Mike", "Microsoft"),
};
Company[] companies =
{
    new Company("Microsoft", "C#"),
    new Company("Google", "Go"),
    new Company("Oracle", "Java")
};
var employees = from p in people
             join c in companies on p.Company equals c.Title
             select new { Name = p.Name, Company = c.Title, Language = c.Language };

foreach (var emp in employees)
    Console.WriteLine($"{emp.Name} - {emp.Company} ({emp.Language})");

record class Person(string Name, string Company);
record class Company(string Title, string Language);

С помощью выражения

join c in companies on p.Company equals c.Title

объект p из списка people (то есть объект Person) соединяется с объектом c из списка companies (то есть с объектом Company), если значение свойства p.Company совпадает со значением свойства c.Title. Результатом соединения будет объект анонимного типа, который будет содержать три свойства. В итоге мы получим следующий вывод:

Tom - Microsoft (C#)
Sam - Google (Go)
Mike - Microsoft (C#)

Обратите внимание, что в массиве people есть объект new Person("Bob", "JetBrains"), но в массиве компаний компании с именем "JetBrains" нет, соответственно он не попал с результат. Аналогично в списке people нет объектов Person, которые бы соотствовали компании new Company("Oracle", "Java").

Метод Join

В качестве альтернативы можно было бы использовать метод Join():

Join(IEnumerable<TInner> inner, 
    Func<TOuter,TKey> outerKeySelector, 
    Func<TInner,TKey> innerKeySelector, 
    Func<TOuter,TInner,TResult> resultSelector);

Метод Join() принимает четыре параметра:

  • второй список, который соединяем с текущим

  • делегат, который определяет свойство объекта из текущего списка, по которому идет соединение

  • делегат, который определяет свойство объекта из второго списка, по которому идет соединение

  • делегат, который определяет новый объект в результате соединения

Перепишим предыдущий пример с использованием метода Join:

Person[] people =
{
    new Person("Tom", "Microsoft"), new Person("Sam", "Google"),
    new Person("Bob", "JetBrains"), new Person("Mike", "Microsoft"),
};
Company[] companies =
{
    new Company("Microsoft", "C#"),
    new Company("Google", "Go"),
    new Company("Oracle", "Java")
};
var employees = people.Join(companies, // второй набор
             p => p.Company, // свойство-селектор объекта из первого набора
             c => c.Title, // свойство-селектор объекта из второго набора
             (p, c) => new { Name = p.Name, Company = c.Title, Language = c.Language }); // результат

foreach (var emp in employees)
    Console.WriteLine($"{emp.Name} - {emp.Company} ({emp.Language})");

record class Person(string Name, string Company);
record class Company(string Title, string Language);

GroupJoin

Метод GroupJoin() кроме соединения последовательностей также выполняет и группировку.

GroupJoin(IEnumerable<TInner> inner, 
        Func<TOuter,TKey> outerKeySelector, 
        Func<TInner,TKey> innerKeySelector, 
        Func<TOuter, IEnumerable<TInner>,TResult> resultSelector);

Метод GroupJoin() принимает четыре параметра:

  • второй список, который соединяем с текущим

  • делегат, который определяет свойство объекта из текущей коллекции, по которому идет соединение и по которому будет идти группировка

  • делегат, который определяет свойство объекта из второй коллекции, по которому идет соединение

  • делегат, который определяет новый объект в результате соединения. Этот делегат получает группу - объект текущей коллекции, по которому шла группировка, и набор объектов из второй коллекции, которые сооставляют группу

Например, возьмем выше определенные массивы people и companies и сгуппируем всех пользователей по компаниям:

Person[] people =
{
    new Person("Tom", "Microsoft"), new Person("Sam", "Google"),
    new Person("Bob", "JetBrains"), new Person("Mike", "Microsoft"),
};
Company[] companies =
{
    new Company("Microsoft", "C#"),
    new Company("Google", "Go"),
    new Company("Oracle", "Java")
};
var personnel = companies.GroupJoin(people, // второй набор
             c => c.Title, // свойство-селектор объекта из первого набора
             p => p.Company, // свойство-селектор объекта из второго набора
             (c, employees) => new   // результат
             {
                 Title = c.Title,
                 Employees = employees
            });

foreach (var company in personnel)
{
    Console.WriteLine(company.Title);
    foreach(var emp in company.Employees)
    {
        Console.WriteLine(emp.Name);
    }
    Console.WriteLine();
}

record class Person(string Name, string Company);
record class Company(string Title, string Language);

Результатом выполнения программы будет следующий вывод:

Microsoft
Tom
Mike

Google
Sam

Oracle

Метод GroupJoin, также как и метод Join, принимает все те же параметры. Только теперь в последний параметр - делегат передаются объект компании и набор пользователей этой компании.

Обратите внимание, что для компании "Oracle" в массиве people нет пользователей, хотя для нее также создается группа.

Аналогичного результата можно добитьс и с помощью оператора join:

var personnel = from c in companies
                join p in people on c.Title equals p.Company into g
                select new   // результат
                {
                    Title = c.Title,
                    Employees = g
                };

Метод Zip

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

var courses = new List<Course> { new Course("C#"), new Course("Java") };
var students = new List<Student> { new Student("Tom"), new Student("Bob") };

var enrollments = courses.Zip(students);

foreach (var enrollment in enrollments)
    Console.WriteLine($"{enrollment.First} - {enrollment.Second}");

record class Course(string Title);  // учебный курс
record class Student(string Name);  // студент

Здесь метод Zip объединяет соответствующие элементы из списков courses и students. В результате создается новая коллекция, которая хранит набор кортежей. Каждый кортеж в ней имеет два элемента. Первый элемент из свойства First представляет объект текущей коллекции (в данном случае объект Course), а второй элемент (в свойстве Second) хранит объект второй последовательности (в данном случае объект Student). Консольный вывод:

Course { Title = C# } - Student { Name = Tom }
Course { Title = Java } - Student { Name = Bob }
Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850