Операции с множествами (C#)

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

Внимание

В этих примерах используется System.Collections.Generic.IEnumerable<T> источник данных. Источники данных на основе System.Linq.IQueryProvider используют System.Linq.IQueryable<T> источники данных и деревья выражений. Деревья выражений имеют ограничения на допустимый синтаксис C#. Кроме того, каждый IQueryProvider источник данных, например EF Core , может наложить больше ограничений. Ознакомьтесь с документацией по источнику данных.

Имена методов Описание Синтаксис выражения запроса C# Дополнительные сведения
Distinct или DistinctBy Удаляет повторяющиеся значения из коллекции. Неприменимо. Enumerable.Distinct
Enumerable.DistinctBy
Queryable.Distinct
Queryable.DistinctBy
Except или ExceptBy Возвращает разность множеств, что означает элементы одной коллекции, которые не присутствуют во второй коллекции. Неприменимо. Enumerable.Except
Enumerable.ExceptBy
Queryable.Except
Queryable.ExceptBy
Intersect или IntersectBy Возвращает пересечение множеств, то есть элементы, присутствующие одновременно в обеих коллекциях. Неприменимо. Enumerable.Intersect
Enumerable.IntersectBy
Queryable.Intersect
Queryable.IntersectBy
Union или UnionBy Возвращает объединение множеств, т. е. уникальные элементы, присутствующие в одной из двух коллекций. Неприменимо. Enumerable.Union
Enumerable.UnionBy
Queryable.Union
Queryable.UnionBy

Distinct и DistinctBy.

В следующем примере показано поведение метода Enumerable.Distinct применительно к последовательности строк. Возвращаемая последовательность содержит уникальные элементы из входной последовательности.

Рисунок, показывающий поведение Distinct()

string[] words = ["the", "quick", "brown", "fox", "jumped", "over", "the", "lazy", "dog"];

IEnumerable<string> query = from word in words.Distinct()
                            select word;

foreach (var str in query)
{
    Console.WriteLine(str);
}

/* This code produces the following output:
 *
 * the
 * quick
 * brown
 * fox
 * jumped
 * over
 * lazy
 * dog
 */

Альтернативным способом вместо Distinct может стать DistinctBy, который принимает keySelector. keySelector используется как сравнительный дискриминатор для типа источника. В следующем коде слова разделяются на основе их Length, и отображается первое слово каждой длины.

string[] words = ["the", "quick", "brown", "fox", "jumped", "over", "the", "lazy", "dog"];

foreach (string word in words.DistinctBy(p => p.Length))
{
    Console.WriteLine(word);
}

// This code produces the following output:
//     the
//     quick
//     jumped
//     over

Except и ExceptBy.

В следующем примере показано поведение Enumerable.Except. Возвращаемая последовательность содержит только элементы из первой входной последовательности, которая не входит во вторую входную последовательность.

Рисунок, показывающий действие except()

Примечание.

В следующих примерах в этой статье используются общие источники данных для этой области.
Каждый из них Student имеет уровень оценки, основной отдел и ряд показателей. У него Teacher также есть свойство, указывающее на City кампус, где проводит занятия учитель. У Department есть имя и ссылка на Teacher, который является руководителем отдела.
Пример набора данных можно найти в исходном репозитории.

public enum GradeLevel
{
    FirstYear = 1,
    SecondYear,
    ThirdYear,
    FourthYear
};

public class Student
{
    public required string FirstName { get; init; }
    public required string LastName { get; init; }
    public required int ID { get; init; }

    public required GradeLevel Year { get; init; }
    public required List<int> Scores { get; init; }

    public required int DepartmentID { get; init; }
}

public class Teacher
{
    public required string First { get; init; }
    public required string Last { get; init; }
    public required int ID { get; init; }
    public required string City { get; init; }
}

public class Department
{
    public required string Name { get; init; }
    public int ID { get; init; }

    public required int TeacherID { get; init; }
}

Примечание.

Общие источники данных для этой области см. в статье "Обзор операторов стандартных запросов ".

string[] words1 = ["the", "quick", "brown", "fox"];
string[] words2 = ["jumped", "over", "the", "lazy", "dog"];

IEnumerable<string> query = from word in words1.Except(words2)
                            select word;

foreach (var str in query)
{
    Console.WriteLine(str);
}

/* This code produces the following output:
 *
 * quick
 * brown
 * fox
 */

Вместо ExceptBy можно использовать метод Except, который принимает две последовательности потенциально разнородных типов и keySelector. Это keySelector тот же тип, что и тип первой коллекции. Рассмотрим следующий массив Teacher и идентификаторы преподавателей, которых нужно исключить. Чтобы найти преподавателей в первой коллекции, которая не находится во второй коллекции, можно проецировать идентификатор преподавателя на вторую коллекцию:

int[] teachersToExclude =
[
    901,    // English
    965,    // Mathematics
    932,    // Engineering
    945,    // Economics
    987,    // Physics
    901     // Chemistry
];

foreach (Teacher teacher in
    teachers.ExceptBy(
        teachersToExclude, teacher => teacher.ID))
{
    Console.WriteLine($"{teacher.First} {teacher.Last}");
}

В приведенном выше коде C#:

  • Массив teachers фильтруется, чтобы включать только тех учителей, которых нет в массиве teachersToExclude.
  • Массив teachersToExclude содержит ID значение для всех руководителей отделов.
  • Вызов ExceptBy приводит к образованию нового набора значений, которые записываются в консоль.

Новый набор значений имеет тип Teacher, который соответствует типу первой коллекции. Каждый teacher в массиве teachers, не имеющий соответствующего значения идентификатора в массиве teachersToExclude, записывается в консоль teachersToExclude.

Intersect и IntersectBy.

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

Рисунок, показывающий пересечение двух последовательностей

string[] words1 = ["the", "quick", "brown", "fox"];
string[] words2 = ["jumped", "over", "the", "lazy", "dog"];

IEnumerable<string> query = from word in words1.Intersect(words2)
                            select word;

foreach (var str in query)
{
    Console.WriteLine(str);
}

/* This code produces the following output:
 *
 * the
 */

Вместо IntersectBy можно использовать метод Intersect, который принимает две последовательности потенциально разнородных типов и keySelector. keySelector используется как сравнительный дискриминатор для типа второй коллекции. Рассмотрим следующие массивы учащихся и преподавателей. Запрос соответствует элементам в каждой последовательности по имени, чтобы найти тех учащихся, которые также являются преподавателями:

foreach (Student person in
    students.IntersectBy(
        teachers.Select(t => (t.First, t.Last)), s => (s.FirstName, s.LastName)))
{
    Console.WriteLine($"{person.FirstName} {person.LastName}");
}

В приведенном выше коде C#:

  • Запрос создает пересечение Teacher и Student посредством сравнения имен.
  • В результирующей последовательности присутствуют только люди, найденные в обоих массивах.
  • Полученные экземпляры Student записываются в консоль.

Union и UnionBy.

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

График, показывающий объединение двух последовательностей.

string[] words1 = ["the", "quick", "brown", "fox"];
string[] words2 = ["jumped", "over", "the", "lazy", "dog"];

IEnumerable<string> query = from word in words1.Union(words2)
                            select word;

foreach (var str in query)
{
    Console.WriteLine(str);
}

/* This code produces the following output:
 *
 * the
 * quick
 * brown
 * fox
 * jumped
 * over
 * lazy
 * dog
*/

Вместо UnionBy можно использовать метод Union, который принимает две последовательности одного типа и keySelector. keySelector используется как сравнительный дискриминатор для типа источника. Следующий запрос создает список всех людей, которые являются учащимися или преподавателями. Учащиеся, которые также являются учителями, добавляются в набор профсоюзов только один раз:

foreach (var person in
    students.Select(s => (s.FirstName, s.LastName)).UnionBy(
        teachers.Select(t => (FirstName: t.First, LastName: t.Last)), s => (s.FirstName, s.LastName)))
{
    Console.WriteLine($"{person.FirstName} {person.LastName}");
}

В приведенном выше коде C#:

  • teachers и students массивы объединяются, используя их имена в качестве селектора ключей.
  • Полученные имена записываются в консоль.

См. также