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

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

Методы стандартных операторов запросов, которые выполняют операции над множествами, перечислены в следующем разделе.

Методы

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

Примеры

В некоторых из приведенных ниже примеров используется тип record, который представляет планеты Солнечной системы.

namespace SolarSystem;

record Planet(
    string Name,
    PlanetType Type,
    int OrderFromSun)
{
    public static readonly Planet Mercury =
        new(nameof(Mercury), PlanetType.Rock, 1);

    public static readonly Planet Venus =
        new(nameof(Venus), PlanetType.Rock, 2);

    public static readonly Planet Earth =
        new(nameof(Earth), PlanetType.Rock, 3);

    public static readonly Planet Mars =
        new(nameof(Mars), PlanetType.Rock, 4);

    public static readonly Planet Jupiter =
        new(nameof(Jupiter), PlanetType.Gas, 5);

    public static readonly Planet Saturn =
        new(nameof(Saturn), PlanetType.Gas, 6);

    public static readonly Planet Uranus =
        new(nameof(Uranus), PlanetType.Liquid, 7);

    public static readonly Planet Neptune =
        new(nameof(Neptune), PlanetType.Liquid, 8);

    // Yes, I know... not technically a planet anymore
    public static readonly Planet Pluto =
        new(nameof(Pluto), PlanetType.Ice, 9);
}

Для создания экземпляра позиционной записи record Planet требуются аргументы Name, Type и OrderFromSun. Существует несколько экземпляров планет static readonly типа Planet. Это удобные определения для хорошо известных планет. Элемент Type определяет тип планеты.

namespace SolarSystem;

enum PlanetType
{
    Rock,
    Ice,
    Gas,
    Liquid
};

Distinct и DistinctBy

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

Graphic showing the behavior of Distinct().

string[] planets = { "Mercury", "Venus", "Venus", "Earth", "Mars", "Earth" };

IEnumerable<string> query = from planet in planets.Distinct()
                            select planet;

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

/* This code produces the following output:
 *
 * Mercury
 * Venus
 * Earth
 * Mars
 */

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

Planet[] planets =
{
    Planet.Mercury,
    Planet.Venus,
    Planet.Earth,
    Planet.Mars,
    Planet.Jupiter,
    Planet.Saturn,
    Planet.Uranus,
    Planet.Neptune,
    Planet.Pluto
};

В следующем коде планеты разделяются на основе PlanetType. При этом отображается первая планета каждого типа:

foreach (Planet planet in planets.DistinctBy(p => p.Type))
{
    Console.WriteLine(planet);
}

// This code produces the following output:
//     Planet { Name = Mercury, Type = Rock, OrderFromSun = 1 }
//     Planet { Name = Jupiter, Type = Gas, OrderFromSun = 5 }
//     Planet { Name = Uranus, Type = Liquid, OrderFromSun = 7 }
//     Planet { Name = Pluto, Type = Ice, OrderFromSun = 9 }

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

  • Массив Planet фильтруется по первому вхождению каждого уникального типа планеты.
  • Полученные экземпляры planet записываются в консоль.

Except и ExceptBy

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

Graphic showing the action of Except().

string[] planets1 = { "Mercury", "Venus", "Earth", "Jupiter" };
string[] planets2 = { "Mercury", "Earth", "Mars", "Jupiter" };

IEnumerable<string> query = from planet in planets1.Except(planets2)
                            select planet;

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

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

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

Planet[] planets =
{
    Planet.Mercury,
    Planet.Venus,
    Planet.Earth,
    Planet.Jupiter
};

Planet[] morePlanets =
{
    Planet.Mercury,
    Planet.Earth,
    Planet.Mars,
    Planet.Jupiter
};

Чтобы найти планеты из первой коллекции, которые не входят во вторую коллекцию, можно проецировать имена планет в виде коллекции second с прежним значением keySelector:

// A shared "keySelector"
static string PlanetNameSelector(Planet planet) => planet.Name;

foreach (Planet planet in
    planets.ExceptBy(
        morePlanets.Select(PlanetNameSelector), PlanetNameSelector))
{
    Console.WriteLine(planet);
}

// This code produces the following output:
//     Planet { Name = Venus, Type = Rock, OrderFromSun = 2 }

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

  • keySelector определяется как локальная функция static, которая различает имена планет.
  • Первый массив планет фильтруется, чтобы получить отсутствующие во втором массиве планеты, на основе имен планет.
  • Полученный экземпляр planet записывается в консоль.

Intersect и IntersectBy

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

Graphic showing the intersection of two sequences.

string[] planets1 = { "Mercury", "Venus", "Earth", "Jupiter" };
string[] planets2 = { "Mercury", "Earth", "Mars", "Jupiter" };

IEnumerable<string> query = from planet in planets1.Intersect(planets2)
                            select planet;

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

/* This code produces the following output:
 *
 * Mercury
 * Earth
 * Jupiter
 */

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

Planet[] firstFivePlanetsFromTheSun =
{
    Planet.Mercury,
    Planet.Venus,
    Planet.Earth,
    Planet.Mars,
    Planet.Jupiter
};

Planet[] lastFivePlanetsFromTheSun =
{
    Planet.Mars,
    Planet.Jupiter,
    Planet.Saturn,
    Planet.Uranus,
    Planet.Neptune
};

Существует два массива планет, один из которых представляет первые пять планет от Солнца, а второй — последние пять планет. Так как тип Planet является позиционным типом record, вы можете использовать его семантику сравнения значений в формате keySelector:

foreach (Planet planet in
    firstFivePlanetsFromTheSun.IntersectBy(
        lastFivePlanetsFromTheSun, planet => planet))
{
    Console.WriteLine(planet);
}

// This code produces the following output:
//     Planet { Name = Mars, Type = Rock, OrderFromSun = 4 }
//     Planet { Name = Jupiter, Type = Gas, OrderFromSun = 5 }

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

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

Union и UnionBy

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

Graphic showing the union of two sequences.

string[] planets1 = { "Mercury", "Venus", "Earth", "Jupiter" };
string[] planets2 = { "Mercury", "Earth", "Mars", "Jupiter" };

IEnumerable<string> query = from planet in planets1.Union(planets2)
                            select planet;

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

/* This code produces the following output:
 *
 * Mercury
 * Venus
 * Earth
 * Jupiter
 * Mars
 */

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

Planet[] firstFivePlanetsFromTheSun =
{
    Planet.Mercury,
    Planet.Venus,
    Planet.Earth,
    Planet.Mars,
    Planet.Jupiter
};

Planet[] lastFivePlanetsFromTheSun =
{
    Planet.Mars,
    Planet.Jupiter,
    Planet.Saturn,
    Planet.Uranus,
    Planet.Neptune
};

Чтобы объединить эти две коллекции в одну последовательность, укажите keySelector:

foreach (Planet planet in
    firstFivePlanetsFromTheSun.UnionBy(
        lastFivePlanetsFromTheSun, planet => planet))
{
    Console.WriteLine(planet);
}

// This code produces the following output:
//     Planet { Name = Mercury, Type = Rock, OrderFromSun = 1 }
//     Planet { Name = Venus, Type = Rock, OrderFromSun = 2 }
//     Planet { Name = Earth, Type = Rock, OrderFromSun = 3 }
//     Planet { Name = Mars, Type = Rock, OrderFromSun = 4 }
//     Planet { Name = Jupiter, Type = Gas, OrderFromSun = 5 }
//     Planet { Name = Saturn, Type = Gas, OrderFromSun = 6 }
//     Planet { Name = Uranus, Type = Liquid, OrderFromSun = 7 }
//     Planet { Name = Neptune, Type = Liquid, OrderFromSun = 8 }

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

  • Эти два массива Planet объединяются с использованием семантики сравнения значений record.
  • Полученные экземпляры planet записываются в консоль.

См. также