CodeGym /Cursos /C# SELF /Operações Union, <...

Operações Union, Intersect, Except no LINQ

C# SELF
Nível 33 , Lição 3
Disponível

1. Introdução

Bora pra um assunto muito massa e super usado na prática: operações de conjuntos no LINQ — Union, Intersect, Except. Com elas, dá pra mexer com coleções como se fosse álgebra de conjuntos (ou contar figurinhas de dois pacotes na mesa do dev — que, convenhamos, é quase a mesma coisa). Se você nunca ouviu falar de álgebra de conjuntos (ou esqueceu), pode parecer complicado. Mas é bem simples: imagina dois sacos de frutas. Álgebra de conjuntos é só um jeito de descobrir quais frutas tem nos dois sacos, quais só em um, e quais aparecem se juntar tudo. Tipo brincar com figurinhas: dá pra juntar tudo, achar as repetidas ou tirar umas das outras. É isso aí.

Na prática: Esses métodos são ótimos quando você precisa juntar resultados de duas consultas diferentes, achar elementos em comum ou ver a diferença entre coleções. Por exemplo:

  • Montar uma lista geral de todos os produtos que aparecem na lista de vendas ou na de compras.
  • Achar produtos que estão nos dois lugares — os itens em comum.
  • Ou descobrir quais produtos estão no estoque mas não foram vendidos — alguém tá encalhado aí!

Problemas reais: Álgebra de conjuntos aparece em tudo: filtrar usuários por assinatura, achar quem tá em grupos diferentes, buscar pedidos únicos ou cruzados, comparar resultados de duas queries no banco e por aí vai.

2. Operação Union — juntando coleções

É simples, então bora direto pro código. Imagina que temos duas listas de produtos:

List<string> warehouseProducts = new List<string> { "Leite", "Pão", "Queijo", "Ovos" };
List<string> recentlySold = new List<string> { "Pão", "Queijo", "Salame", "Chá" };

O que faz o Union?

Union retorna os elementos únicos que aparecem em pelo menos uma das coleções.
Ou seja, é a união.

var allProducts = warehouseProducts.Union(recentlySold);

foreach (var product in allProducts)
{
    Console.WriteLine(product);
}
// Vai mostrar: Leite, Pão, Queijo, Ovos, Salame, Chá

Visualmente, fica assim:

Estoque Vendido Union (União)
Leite Pão Leite
Pão Queijo Pão
Queijo Salame Queijo
Ovos Chá Ovos
Salame
Chá

Na álgebra de conjuntos, Union é o "ou": me dá tudo que tem em qualquer lugar. Imagina duas caixas com tipos diferentes de chá — se você quiser provar todos os sabores, não importa se algum repete — você só toma uma vez.

Union remove duplicatas automaticamente (baseado no Equals e GetHashCode do tipo do elemento). Se você fizer união com suas próprias classes (tipo Product), garante que esses métodos tão certinhos, senão o Union pode agir estranho: elementos iguais podem ser considerados diferentes!

Não esquece: a ordem dos elementos no resultado mantém a ordem da primeira coleção, e os novos aparecem no final (na ordem que aparecem na segunda coleção).

Exemplo com objetos

Continuando nosso app "Loja". Temos duas listas de Product:

public class Product
{
    public string Name { get; set; }
    public string Category { get; set; }

    // Pra funcionar direito Union/Intersect/Except, tem que ter Equals e GetHashCode bem feitos!
    public override bool Equals(object? obj) =>
        obj is Product other && Name == other.Name && Category == other.Category;

    public override int GetHashCode() => HashCode.Combine(Name, Category);
}

List<Product> stock = new()
{
    new Product { Name = "Leite", Category = "Laticínios" },
    new Product { Name = "Pão", Category = "Padaria" },
};

List<Product> sold = new()
{
    new Product { Name = "Pão", Category = "Padaria" },
    new Product { Name = "Salame", Category = "Embutidos" },
};

var all = stock.Union(sold);

// Repara: não duplica elementos,
// mesmo que "pareçam" iguais, mas tem que ser iguais pela lógica.

foreach (var p in all)
    Console.WriteLine($"{p.Name} ({p.Category})");

Erro comum: Se não sobrescrever Equals/GetHashCode, o Union vai achar que instâncias diferentes com os mesmos valores são elementos diferentes!

3. Operação Intersect — cruzando coleções

Intersect retorna os elementos que aparecem em todas as coleções de origem. É tipo o "e" da álgebra de conjuntos.

Exemplo

Lembra das listas do exemplo anterior:

var commonProducts = warehouseProducts.Intersect(recentlySold);
foreach (var product in commonProducts)
{
    Console.WriteLine(product);
}
// Vai mostrar: Pão, Queijo

Visualização:

Estoque Vendido Intersect (Comum)
Pão Pão Pão
Queijo Queijo Queijo

Quando usar?

  • Você quer saber quais produtos do seu estoque foram vendidos hoje.
  • Em entrevistas sempre perguntam: "Como acha a interseção de duas listas?" — LINQ faz isso em uma linha!
  • Em apps de negócio, interseção é útil pra filtrar por critérios combinados.

Peculiaridades e erros comuns

Se um elemento aparece várias vezes na coleção, no resultado só vai ter uma vez.
Pra objetos complexos (tipo Product), de novo, precisa de Equals/GetHashCode bem feitos.

Exemplo com objetos

var commonObjects = stock.Intersect(sold);
foreach (var p in commonObjects)
    Console.WriteLine($"{p.Name} ({p.Category})");
// Vai mostrar só Pão (Padaria)

4. Operação Except — diferença de coleções

Except retorna os elementos que estão só na primeira coleção, mas não na segunda.

Exemplo

var productsOnlyInStock = warehouseProducts.Except(recentlySold);
foreach (var product in productsOnlyInStock)
{
    Console.WriteLine(product);
}
// Vai mostrar: Leite, Ovos

Ou seja, são os produtos que estão no estoque, mas não foram vendidos.

Visualização:

Estoque Vendido Except (só estoque)
Leite Leite
Ovos Ovos
Pão Pão (ignorado)
Queijo Queijo (ignorado)

Analogia

É como se você tirasse da sua pilha todos os documentos que já enviou — só sobra o que ainda tá com você e em nenhum outro lugar.

Exemplo com objetos

var unsold = stock.Except(sold);
foreach (var p in unsold)
    Console.WriteLine($"{p.Name} ({p.Category})");
// Vai mostrar: Leite (Laticínios)

! Pontos não óbvios

O método Except liga pra ordem: A.Except(B) não é igual a B.Except(A)! A primeira coleção é "de onde tira", a segunda é "o que remove".

5. Juntando e combinando várias operações LINQ

Tem hora que uma operação só não resolve. Por exemplo, você quer mostrar produtos que estão só no estoque ou só nos vendidos — mas não nos dois ao mesmo tempo (isso é a "diferença simétrica").

Diferença simétrica ("XOR" de conjuntos):

var onlyInOne = warehouseProducts.Except(recentlySold)
                   .Union(recentlySold.Except(warehouseProducts));
foreach (var product in onlyInOne)
{
    Console.WriteLine(product);
}
// Vai mostrar: Leite, Ovos, Salame, Chá

Pra lógicas mais complexas, é massa combinar métodos LINQ:

// Achar produtos que não estão nem no estoque nem nos vendidos, só na lista "aguardando chegada"
List<string> expected = new() { "Café", "Chá", "Leite" };
var onlyExpected = expected.Except(warehouseProducts.Union(recentlySold));
foreach (var product in onlyExpected)
    Console.WriteLine(product);
// Vai mostrar: Café

6. Trabalhando com tipos customizados e IEqualityComparer

Às vezes não precisa comparar objetos por todos os campos: por exemplo, só o nome do produto importa, a categoria tanto faz. Pra isso, os métodos LINQ aceitam um parâmetro extra — IEqualityComparer<T>, que define como comparar os elementos.

Exemplo de comparador próprio:

class ProductNameComparer : IEqualityComparer<Product>
{
    public bool Equals(Product? x, Product? y) => x?.Name == y?.Name;
    public int GetHashCode(Product obj) => obj.Name.GetHashCode();
}

var comp = new ProductNameComparer();
var uniqueByName = stock.Union(sold, comp);
foreach (var p in uniqueByName)
    Console.WriteLine(p.Name); // Leite, Pão, Salame

Isso é ótimo se você não quer mexer no Equals/GetHashCode da model inteira, só quer comparar objetos por uma regra específica.

7. Esqueminhas e tabelas visuais

Resumindo os usos (pras listas A e B):

Operação Resultado
A.Union(B)
Tudo que tem em A ou B (elementos únicos).
A.Intersect(B)
Tudo que tem nos dois (A e B).
A.Except(B)
Tudo que tem só em A, mas não em B.

Esquema (Venn Diagram, se desse pra desenhar):


      [A]        [B]
      oooooooooo
      oooooooo oooo
      ooooo oo   ooo
      ooo    ooo  oo
      oo      o  ooo
      ooo    ooo ooo
  • Union: tudo que tá dentro dos dois círculos.
  • Intersect: só o meio (interseção).
  • Except: só o que tá no círculo A, sem pegar o que cruza com B.

8. Erros comuns usando Union, Intersect e Except

Erro #1: usar com objetos sem Equals e GetHashCode.
Se sua classe não sobrescreve esses métodos, os métodos Union, Intersect e Except vão funcionar errado: objetos iguais no conteúdo vão ser considerados diferentes. O resultado pode ser inesperado (e geralmente inútil).

Erro #2: tentar comparar objetos por parte dos campos sem IEqualityComparer.
Tipo, se você quer comparar produtos só pelo nome, não pela estrutura toda, o Intersect não vai entender sozinho. Sem um IEqualityComparer explícito, o resultado não bate com o esperado.

Erro #3: esperar que a ordem dos elementos seja sempre igual.
Muita gente acha que a coleção final mantém a ordem da união ou interseção. Mas depende do método: Union mantém a ordem da primeira coleção, mas Intersect e Except podem devolver em ordem aleatória. Melhor não confiar na ordem.

Erro #4: ignorar performance com coleções grandes.
Se os dados são grandes, esses métodos podem ser lentos. Pense em agregar, filtrar antes ou usar estruturas de hash (tipo HashSet) pra acelerar.

Comentários
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION