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

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

Внимание

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

Имена методов Description Синтаксис выражения запроса 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
 */

Вместо Except можно использовать метод ExceptBy, который принимает две последовательности потенциально разнородных типов и 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 консоль.

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
 */

Вместо Intersect можно использовать метод IntersectBy, который принимает две последовательности потенциально разнородных типов и 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
*/

Вместо Union можно использовать метод UnionBy, который принимает две последовательности одного типа и 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#:

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

См. также