Operações de conjunto (C#)

Definir operações no LINQ referem-se a operações de consulta que produzem um conjunto de resultados baseado na presença ou ausência de elementos equivalentes dentro das mesmas coleções ou coleções separadas.

Importante

Esses exemplos usam uma fonte de dados System.Collections.Generic.IEnumerable<T>. Fontes de dados baseadas em System.Linq.IQueryProvider usam as fontes de dados System.Linq.IQueryable<T> e as árvores de expressão. As árvores de expressão possuem limitações na sintaxe C# permitida. Além disso, todas as fontes de dados IQueryProvider, como EF Core, podem impor mais restrições. Verifique a documentação da fonte de dados.

Nomes de método Descrição Sintaxe de expressão de consulta em C# Mais informações
Distinct ou DistinctBy Remove os valores duplicados de uma coleção. Não aplicável. Enumerable.Distinct
Enumerable.DistinctBy
Queryable.Distinct
Queryable.DistinctBy
Except ou ExceptBy Retorna a diferença de conjunto, que significa os elementos de uma coleção que não aparecem em uma segunda coleção. Não aplicável. Enumerable.Except
Enumerable.ExceptBy
Queryable.Except
Queryable.ExceptBy
Intersect ou IntersectBy Retorna a interseção de conjunto, o que significa os elementos que aparecem em cada uma das duas coleções. Não aplicável. Enumerable.Intersect
Enumerable.IntersectBy
Queryable.Intersect
Queryable.IntersectBy
Union ou UnionBy Retorna a união de conjunto, o que significa os elementos únicos que aparecem em qualquer uma das duas coleções. Não aplicável. Enumerable.Union
Enumerable.UnionBy
Queryable.Union
Queryable.UnionBy

Distinct e DistinctBy

O exemplo a seguir descreve o comportamento do método Enumerable.Distinct em uma sequência de cadeias de caracteres. A sequência retornada contém os elementos exclusivos da sequência de entrada.

Ilustração mostrando o comportamento 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
 */

DistinctBy é uma abordagem alternativa a Distinct que usa keySelector. keySelector é usado como o discriminador comparativo do tipo de origem. No seguinte código, as palavras são discriminados com base no respectivo Length, e a primeira palavra de cada tipo é exibida:

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 e ExceptBy

O exemplo a seguir descreve o comportamento de Enumerable.Except. A sequência retornada contém apenas os elementos da primeira sequência de entrada que não estão na segunda sequência de entrada.

Ilustração mostrando a ação Except()

Observação

Os exemplos a seguir neste artigo usam as fontes de dados comuns para essa área.
Cada Student tem um nível de escolaridade, um departamento primário e uma série de pontuações. Um Teacher também tem uma propriedade City que identifica o campus onde o docente ministra aulas. A Department tem um nome e uma referência a um Teacher que atua como chefe do departamento.
Você pode encontrar o conjunto de dados de exemplo no repositório de origem.

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

O método ExceptBy é uma abordagem alternativa a Except que usa duas sequências de tipos possivelmente heterogêneos e um keySelector. O keySelector é o mesmo tipo que o tipo da primeira coleção. Considere a matriz Teacher a seguir e IDs de professor a serem excluídas. Para encontrar professores na primeira coleção que não estão na segunda coleção, você pode projetar a ID do professor na segunda coleção:

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}");
}

No código anterior do C#:

  • A matriz teachers é filtrada apenas para os professores que não estão na matriz teachersToExclude.
  • A matriz teachersToExclude contém o valor ID de todos os chefes de departamento.
  • A chamada para ExceptBy resulta em um novo conjunto de valores que são gravados no console.

O novo conjunto de valores é do tipo Teacher, que é o tipo da primeira coleção. Cada teacher na matriz teachers que não tem um valor de ID correspondente na matriz teachersToExclude é gravada no console.

Intersect e IntersectBy

O exemplo a seguir descreve o comportamento de Enumerable.Intersect. A sequência retornada contém os elementos que são comuns a ambas as sequências de entrada.

Ilustração mostrando a interseção de duas sequências

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

O método IntersectBy é uma abordagem alternativa a Intersect que usa duas sequências de tipos possivelmente heterogêneos e um keySelector. O keySelector é usado como o discriminatório comparativo do tipo da segunda coleção. Considere as matrizes de alunos e professores a seguir. A consulta corresponde aos itens em cada sequência por nome para localizar os alunos que também são professores:

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

No código anterior do C#:

  • A consulta produz a interseção do Teacher e Student comparando nomes.
  • Somente as pessoas encontradas em ambas as matrizes estão presentes na sequência resultante.
  • As instâncias resultantes Student são gravadas no console.

Union e UnionBy

O exemplo a seguir descreve uma operação de união em duas sequências de cadeias de caracteres. A sequência retornada contém os elementos exclusivos das duas sequências de entrada.

Gráfico mostrando a união de duas sequências.

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

O método UnionBy é uma abordagem alternativa a Union que usa duas sequências do mesmo tipo e uma keySelector. keySelector é usado como o discriminador comparativo do tipo de origem. A consulta a seguir produz a lista de todas as pessoas que são alunos ou professores. Os alunos que também são professores são adicionados ao conjunto sindical apenas uma vez:

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}");
}

No código anterior do C#:

  • As matrizes teachers e students são tecidas usando os nomes delas como o seletor de chaves.
  • OS nomes resultantes são gravados no console.

Confira também