This article has been localized into Portuguese by the community.
Agrupando informações: o método GroupBy()
Até agora, trabalhamos principalmente com listas de dados. Nós classificamos, limitamos e moldamos em novos objetos, mas uma operação importante ainda está faltando: Agrupamento de dados. Quando você agrupa dados, você pega uma lista de algo e a divide em vários grupos, com base em uma ou várias propriedades. Imagine que tenhamos uma fonte de dados como esta:
var users = new List<User>()
{
new User { Name = "John Doe", Age = 42, HomeCountry = "USA" },
new User { Name = "Jane Doe", Age = 38, HomeCountry = "USA" },
new User { Name = "Joe Doe", Age = 19, HomeCountry = "Germany" },
new User { Name = "Jenna Doe", Age = 19, HomeCountry = "Germany" },
new User { Name = "James Doe", Age = 8, HomeCountry = "USA" },
};
Uma lista simples de objetos do usuário, mas pode ser interessante agrupar esses usuários em, e. seu país de origem ou sua idade. Com o LINQ, isso é muito fácil, embora o uso do método GroupBy() possa ser um pouco confuso no começo. Vamos dar uma olhada em como funciona:
using System;
using System.Collections.Generic;
using System.Linq;
namespace LinqGroup
{
class Program
{
static void Main(string[] args)
{
var users = new List<User>()
{
new User { Name = "John Doe", Age = 42, HomeCountry = "USA" },
new User { Name = "Jane Doe", Age = 38, HomeCountry = "USA" },
new User { Name = "Joe Doe", Age = 19, HomeCountry = "Germany" },
new User { Name = "Jenna Doe", Age = 19, HomeCountry = "Germany" },
new User { Name = "James Doe", Age = 8, HomeCountry = "USA" },
};
var usersGroupedByCountry = users.GroupBy(user => user.HomeCountry);
foreach(var group in usersGroupedByCountry)
{
Console.WriteLine("Users from " + group.Key + ":");
foreach(var user in group)
Console.WriteLine("* " + user.Name);
}
}
public class User
{
public string Name { get; set; }
public int Age { get; set; }
public string HomeCountry { get; set; }
}
}
}
A saída resultante será algo como isto:
Users from USA:
* John Doe
* Jane Doe
* James Doe
Users from Germany:
* Joe Doe
* Jenna Doe
O exemplo pode parecer um pouco longo, mas como você logo perceberá, a maior parte está apenas preparando a fonte de dados. Lembre-se de que todos os dados podem vir de um documento XML ou de um banco de dados - é mais fácil demonstrar com uma fonte de dados de objeto que você pode usar como está.
A parte interessante é quando criamos a variável usersGroupedByCountry . Fazemos isso chamando o método GroupBy() em nossa fonte de dados, fornecendo o parâmetro que queremos agrupar os dados. Neste caso, quero que os usuários sejam agrupados por seu país de origem, de modo que é a propriedade que eu forneço ao método GroupBy(). O resultado é um objeto com uma propriedade Key, que contém o valor da propriedade que nós agrupamos por (HomeCountry, neste caso), bem como todos os objetos que pertencem ao grupo. Usamos isso nas próximas linhas para fazer uma iteração nos grupos que acabamos de criar e, para cada grupo, imprimimos a chave (HomeCountry) e, em seguida, iteramos e imprimimos todos os objetos User do grupo.
Chaves de grupos personalizados
Como você pode ver, o agrupamento por uma propriedade existente é fácil, mas como você já deve ter percebido, os métodos LINQ são muito flexíveis. É tão simples criar seus próprios grupos personalizados com base no que você quiser - um exemplo disso pode ser o seguinte, onde criamos grupos com base nas duas primeiras letras do nome do usuário:
using System;
using System.Collections.Generic;
using System.Linq;
namespace LinqGroup
{
class Program
{
static void Main(string[] args)
{
var users = new List<User>()
{
new User { Name = "John Doe", Age = 42, HomeCountry = "USA" },
new User { Name = "Jane Doe", Age = 38, HomeCountry = "USA" },
new User { Name = "Joe Doe", Age = 19, HomeCountry = "Germany" },
new User { Name = "Jenna Doe", Age = 19, HomeCountry = "Germany" },
new User { Name = "James Doe", Age = 8, HomeCountry = "USA" },
};
var usersGroupedByFirstLetters = users.GroupBy(user => user.Name.Substring(0, 2));
foreach(var group in usersGroupedByFirstLetters)
{
Console.WriteLine("Users starting with " + group.Key + ":");
foreach(var user in group)
Console.WriteLine("* " + user.Name);
}
}
public class User
{
public string Name { get; set; }
public int Age { get; set; }
public string HomeCountry { get; set; }
}
}
}
Nós simplesmente chamamos o método Substring() no nome, para obter as duas primeiras letras, e então o LINQ cria os grupos de usuários baseados nele. O resultado será algo parecido com isto:
Users starting with Jo:
* John Doe
* Joe Doe
Users starting with Ja:
* Jane Doe
* James Doe
Users starting with Je:
* Jenna Doe
Então, como você pode ver, estamos livres para chamar um método dentro do método GroupBy() - na verdade, podemos fazer praticamente o que quisermos, desde que retornemos algo que o LINQ possa usar para agrupar os itens. Podemos até criar um método que retorna uma nova informação sobre o item e usá-lo para criar um grupo, como fazemos no próximo exemplo:
using System;
using System.Collections.Generic;
using System.Linq;
namespace LinqGroup
{
class Program
{
static void Main(string[] args)
{
var users = new List<User>()
{
new User { Name = "John Doe", Age = 42, HomeCountry = "USA" },
new User { Name = "Jane Doe", Age = 38, HomeCountry = "USA" },
new User { Name = "Joe Doe", Age = 19, HomeCountry = "Germany" },
new User { Name = "Jenna Doe", Age = 19, HomeCountry = "Germany" },
new User { Name = "James Doe", Age = 8, HomeCountry = "USA" },
};
var usersGroupedByAgeGroup = users.GroupBy(user => user.GetAgeGroup());
foreach(var group in usersGroupedByAgeGroup)
{
Console.WriteLine(group.Key + ":");
foreach(var user in group)
Console.WriteLine("* " + user.Name + " [" + user.Age + " years]");
}
}
public class User
{
public string Name { get; set; }
public int Age { get; set; }
public string HomeCountry { get; set; }
public string GetAgeGroup()
{
if (this.Age < 13)
return "Children";
if (this.Age < 20)
return "Teenagers";
return "Adults";
}
}
}
}
Observe como implementei um método GetAgeGroup() na classe User. Ele retorna uma string que define o grupo etário do usuário e nós simplesmente o chamamos no método GroupBy() para usá-lo como uma chave de grupo. O resultado será assim:
Adults:
* John Doe [42 years]
* Jane Doe [38 years]
Teenagers:
* Joe Doe [19 years]
* Jenna Doe [19 years]
Children:
* James Doe [8 years]
Eu escolho implementar o método GetAgeGroup() na classe User, porque pode ser útil em outros lugares, mas às vezes você só precisa de um pequeno pedaço de lógica para criar os grupos, para não ser reutilizado em outro lugar. Nessas situações, você está livre para fornecer a lógica diretamente ao método GroupBy(), como uma expressão lambda, assim:
var usersGroupedByAgeGroup = users.GroupBy(user =>
{
if (user.Age < 13)
return "Children";
if (user.Age < 20)
return "Teenagers";
return "Adults";
});
O resultado é claro o mesmo!
Agrupar por uma chave composta
Até agora, as chaves dos nossos grupos foram apenas um valor único, por ex. uma propriedade ou o resultado de uma chamada de método. No entanto, você é livre para criar suas próprias chaves que contêm vários valores - elas são chamadas de chaves compostas. Um exemplo de uso poderia ser se quiséssemos agrupar nossos usuários com base no país de origem e na idade deles, assim:
using System;
using System.Collections.Generic;
using System.Linq;
namespace LinqGroup2
{
class Program
{
static void Main(string[] args)
{
var users = new List<User>()
{
new User { Name = "John Doe", Age = 42, HomeCountry = "USA" },
new User { Name = "Jane Doe", Age = 38, HomeCountry = "USA" },
new User { Name = "Joe Doe", Age = 19, HomeCountry = "Germany" },
new User { Name = "Jenna Doe", Age = 19, HomeCountry = "Germany" },
new User { Name = "James Doe", Age = 8, HomeCountry = "USA" },
};
var usersGroupedByCountryAndAge = users.GroupBy(user => new { user.HomeCountry, user.Age });
foreach(var group in usersGroupedByCountryAndAge)
{
Console.WriteLine("Users from " + group.Key.HomeCountry + " at the age of " + group.Key.Age + ":");
foreach (var user in group)
Console.WriteLine("* " + user.Name + " [" + user.Age + " years]");
}
}
public class User
{
public string Name { get; set; }
public int Age { get; set; }
public string HomeCountry { get; set; }
}
}
}
Observe a sintaxe que usamos no método GroupBy() - em vez de fornecer uma única propriedade, criamos um novo objeto anônimo, que contém as propriedades HomeCountry e Age. O LINQ agora criará grupos com base nessas duas propriedades e anexará o objeto anônimo à propriedade Key do grupo. Somos livres para usar as duas propriedades quando fazemos uma iteração nos grupos, como você pode ver. O resultado será algo parecido com isto:
Users from USA at the age of 42:
* John Doe [42 years]
Users from USA at the age of 38:
* Jane Doe [38 years]
Users from Germany at the age of 19:
* Joe Doe [19 years]
* Jenna Doe [19 years]
Users from USA at the age of 8:
* James Doe [8 years]
Como sempre, usamos a sintaxe do Método LINQ através deste artigo, mas permitimos que você forneça um exemplo de comparação sobre como isso pode ser feito com a sintaxe LINQ Query:
// Method syntax
var usersGroupedByCountryAndAge = users.GroupBy(user => new { user.HomeCountry, user.Age });
// Query syntax
var usersGroupedByCountryAndAgeQ = from user in users group user by new { user.HomeCountry, user.Age } into userGroup select userGroup;
Resumo
Como você provavelmente pode ver nos exemplos deste artigo, o método GroupBy() do LINQ é extremamente poderoso. Ele realmente permite que você use seus dados de novas maneiras, com muito pouco código. Anteriormente, isso seria muito complicado ou exigiria um banco de dados relacional, mas, com o LINQ, você pode usar qualquer fonte de dados que desejar e ainda obter a mesma funcionalidade fácil de usar.