Cláusula group (Referência de C#)group clause (C# Reference)

A cláusula group retorna uma sequência de objetos IGrouping<TKey,TElement> que contêm zero ou mais itens que correspondem ao valor de chave do grupo.The group clause returns a sequence of IGrouping<TKey,TElement> objects that contain zero or more items that match the key value for the group. Por exemplo, é possível agrupar uma sequência de cadeias de caracteres de acordo com a primeira letra de cada cadeia de caracteres.For example, you can group a sequence of strings according to the first letter in each string. Nesse caso, a primeira letra é a chave, tem um tipo char e é armazenada na propriedade Key de cada objeto IGrouping<TKey,TElement>.In this case, the first letter is the key and has a type char, and is stored in the Key property of each IGrouping<TKey,TElement> object. O compilador infere o tipo da chave.The compiler infers the type of the key.

É possível finalizar uma expressão de consulta com uma cláusula group, conforme mostrado no exemplo a seguir:You can end a query expression with a group clause, as shown in the following example:

// Query variable is an IEnumerable<IGrouping<char, Student>>
var studentQuery1 =
    from student in students
    group student by student.Last[0];

Caso deseje executar mais operações de consulta em cada grupo, é possível especificar um identificador temporário usando a palavra-chave contextual into.If you want to perform additional query operations on each group, you can specify a temporary identifier by using the into contextual keyword. Ao usar into, é necessário continuar a consulta e, em algum momento, finalizá-la com uma instrução select ou outra cláusula group, conforme mostrado no trecho a seguir:When you use into, you must continue with the query, and eventually end it with either a select statement or another group clause, as shown in the following excerpt:

// Group students by the first letter of their last name
// Query variable is an IEnumerable<IGrouping<char, Student>>
var studentQuery2 =
    from student in students
    group student by student.Last[0] into g
    orderby g.Key
    select g;

Exemplos mais completos do uso de group com e sem into serão apresentados na seção Exemplo deste artigo.More complete examples of the use of group with and without into are provided in the Example section of this article.

Enumerando os resultados de uma consulta de grupoEnumerating the results of a group query

Como os objetos IGrouping<TKey,TElement> produzidos por uma consulta group são essencialmente uma lista de listas, você deve usar um loop aninhado foreach para acessar os itens em cada grupo.Because the IGrouping<TKey,TElement> objects produced by a group query are essentially a list of lists, you must use a nested foreach loop to access the items in each group. O loop externo itera nas chaves de grupo e o loop interno itera em cada item do grupo em si.The outer loop iterates over the group keys, and the inner loop iterates over each item in the group itself. Um grupo pode ter uma chave sem nenhum elemento.A group may have a key but no elements. Este é o loop foreach que executa a consulta nos exemplos de código anteriores:The following is the foreach loop that executes the query in the previous code examples:

// Iterate group items with a nested foreach. This IGrouping encapsulates
// a sequence of Student objects, and a Key of type char.
// For convenience, var can also be used in the foreach statement.
foreach (IGrouping<char, Student> studentGroup in studentQuery2)
{
     Console.WriteLine(studentGroup.Key);
     // Explicit type for student could also be used here.
     foreach (var student in studentGroup)
     {
         Console.WriteLine("   {0}, {1}", student.Last, student.First);
     }
 }

Tipos de chaveKey types

As chaves de grupo podem ser de qualquer tipo, como uma cadeia de caracteres, um tipo numérico interno, um tipo nomeado definido pelo usuário ou um tipo anônimo.Group keys can be any type, such as a string, a built-in numeric type, or a user-defined named type or anonymous type.

Agrupar por cadeia de caracteresGrouping by string

Os exemplos de código anteriores usaram um char.The previous code examples used a char. Em vez disso, uma chave de cadeia de caracteres pode facilmente ter sido especificada, por exemplo, o sobrenome completo:A string key could easily have been specified instead, for example the complete last name:

// Same as previous example except we use the entire last name as a key.
// Query variable is an IEnumerable<IGrouping<string, Student>>
var studentQuery3 =
    from student in students
    group student by student.Last;

Agrupar por boolGrouping by bool

O exemplo a seguir mostra o uso de um valor booliano para uma chave dividir os resultados em dois grupos.The following example shows the use of a bool value for a key to divide the results into two groups. Observe que o valor é produzido por uma subexpressão na cláusula group.Note that the value is produced by a sub-expression in the group clause.

class GroupSample1
{
    // The element type of the data source.
    public class Student
    {
        public string First { get; set; }
        public string Last { get; set; }
        public int ID { get; set; }
        public List<int> Scores;
    }

    public static List<Student> GetStudents()
    {
        // Use a collection initializer to create the data source. Note that each element
        //  in the list contains an inner sequence of scores.
        List<Student> students = new List<Student>
        {
           new Student {First="Svetlana", Last="Omelchenko", ID=111, Scores= new List<int> {97, 72, 81, 60}},
           new Student {First="Claire", Last="O'Donnell", ID=112, Scores= new List<int> {75, 84, 91, 39}},
           new Student {First="Sven", Last="Mortensen", ID=113, Scores= new List<int> {99, 89, 91, 95}},
           new Student {First="Cesar", Last="Garcia", ID=114, Scores= new List<int> {72, 81, 65, 84}},
           new Student {First="Debra", Last="Garcia", ID=115, Scores= new List<int> {97, 89, 85, 82}}
        };

        return students;
    }

    static void Main()
    {
        // Obtain the data source.
        List<Student> students = GetStudents();

        // Group by true or false.
        // Query variable is an IEnumerable<IGrouping<bool, Student>>
        var booleanGroupQuery =
            from student in students
            group student by student.Scores.Average() >= 80; //pass or fail!

        // Execute the query and access items in each group
        foreach (var studentGroup in booleanGroupQuery)
        {
            Console.WriteLine(studentGroup.Key == true ? "High averages" : "Low averages");
            foreach (var student in studentGroup)
            {
                Console.WriteLine("   {0}, {1}:{2}", student.Last, student.First, student.Scores.Average());
            }
        }

        // Keep the console window open in debug mode.
        Console.WriteLine("Press any key to exit.");
        Console.ReadKey();
    }
}
/* Output:
  Low averages
   Omelchenko, Svetlana:77.5
   O'Donnell, Claire:72.25
   Garcia, Cesar:75.5
  High averages
   Mortensen, Sven:93.5
   Garcia, Debra:88.25
*/

Agrupar por alcance numéricoGrouping by numeric range

O próximo exemplo usa uma expressão para criar chaves de grupo numéricas que representam um intervalo de percentil.The next example uses an expression to create numeric group keys that represent a percentile range. Observe o uso de let como um local conveniente para armazenar um resultado de chamada de método, para que não seja necessário chamar o método duas vezes na cláusula group.Note the use of let as a convenient location to store a method call result, so that you don't have to call the method two times in the group clause. Para obter mais informações sobre como usar métodos com segurança em expressões de consulta, consulte Lidar com exceções em expressões de consulta.For more information about how to safely use methods in query expressions, see Handle exceptions in query expressions.

class GroupSample2
{
    // The element type of the data source.
    public class Student
    {
        public string First { get; set; }
        public string Last { get; set; }
        public int ID { get; set; }
        public List<int> Scores;
    }

    public static List<Student> GetStudents()
    {
        // Use a collection initializer to create the data source. Note that each element
        //  in the list contains an inner sequence of scores.
        List<Student> students = new List<Student>
        {
           new Student {First="Svetlana", Last="Omelchenko", ID=111, Scores= new List<int> {97, 72, 81, 60}},
           new Student {First="Claire", Last="O'Donnell", ID=112, Scores= new List<int> {75, 84, 91, 39}},
           new Student {First="Sven", Last="Mortensen", ID=113, Scores= new List<int> {99, 89, 91, 95}},
           new Student {First="Cesar", Last="Garcia", ID=114, Scores= new List<int> {72, 81, 65, 84}},
           new Student {First="Debra", Last="Garcia", ID=115, Scores= new List<int> {97, 89, 85, 82}}
        };

        return students;
    }

    // This method groups students into percentile ranges based on their
    // grade average. The Average method returns a double, so to produce a whole
    // number it is necessary to cast to int before dividing by 10.
    static void Main()
    {
        // Obtain the data source.
        List<Student> students = GetStudents();

        // Write the query.
        var studentQuery =
            from student in students
            let avg = (int)student.Scores.Average()
            group student by (avg / 10) into g
            orderby g.Key
            select g;

        // Execute the query.
        foreach (var studentGroup in studentQuery)
        {
            int temp = studentGroup.Key * 10;
            Console.WriteLine("Students with an average between {0} and {1}", temp, temp + 10);
            foreach (var student in studentGroup)
            {
                Console.WriteLine("   {0}, {1}:{2}", student.Last, student.First, student.Scores.Average());
            }
        }

        // Keep the console window open in debug mode.
        Console.WriteLine("Press any key to exit.");
        Console.ReadKey();
    }
}
/* Output:
     Students with an average between 70 and 80
       Omelchenko, Svetlana:77.5
       O'Donnell, Claire:72.25
       Garcia, Cesar:75.5
     Students with an average between 80 and 90
       Garcia, Debra:88.25
     Students with an average between 90 and 100
       Mortensen, Sven:93.5
 */

Agrupando por chaves compostasGrouping by composite keys

Use uma chave composta para agrupar elementos de acordo com mais de uma chave.Use a composite key when you want to group elements according to more than one key. Uma chave composta é criada usando um tipo anônimo ou nomeado para armazenar o elemento-chave.You create a composite key by using an anonymous type or a named type to hold the key element. No exemplo a seguir, suponha que uma classe Person foi declarada com membros nomeados surname e city.In the following example, assume that a class Person has been declared with members named surname and city. A cláusula group faz com que um grupo separado seja criado para cada conjunto de pessoas com o mesmo sobrenome e a mesma cidade.The group clause causes a separate group to be created for each set of persons with the same last name and the same city.

group person by new {name = person.surname, city = person.city};

Use um tipo nomeado se for necessário passar a variável de consulta para outro método.Use a named type if you must pass the query variable to another method. Crie uma classe especial usando as propriedades autoimplementadas das chaves e, em seguida, substitua os métodos Equals e GetHashCode.Create a special class using auto-implemented properties for the keys, and then override the Equals and GetHashCode methods. Também é possível usar um struct; nesse caso, não é exatamente necessário substituir esses métodos.You can also use a struct, in which case you do not strictly have to override those methods. Para obter mais informações, consulte Como implementar uma classe leve com propriedades implementadas automaticamente e como consultar arquivos duplicados em uma árvore de diretório.For more information see How to implement a lightweight class with auto-implemented properties and How to query for duplicate files in a directory tree. O último artigo apresenta um exemplo de código que demonstra como usar uma chave composta com um tipo nomeado.The latter article has a code example that demonstrates how to use a composite key with a named type.

ExemploExample

O exemplo a seguir mostra a norma padrão para ordenar dados de origem em grupos quando nenhuma lógica de consulta adicional for aplicada aos grupos.The following example shows the standard pattern for ordering source data into groups when no additional query logic is applied to the groups. Isso é chamado de “agrupamento sem uma continuação”.This is called a grouping without a continuation. Os elementos em uma matriz de cadeias de caracteres são agrupados de acordo com a primeira letra.The elements in an array of strings are grouped according to their first letter. O resultado da consulta é um tipo IGrouping<TKey,TElement> que contém uma propriedade Key pública do tipo char e uma coleção IEnumerable<T> que contém cada item no agrupamento.The result of the query is an IGrouping<TKey,TElement> type that contains a public Key property of type char and an IEnumerable<T> collection that contains each item in the grouping.

O resultado de uma cláusula group é uma sequência de sequências.The result of a group clause is a sequence of sequences. Portanto, para acessar os elementos individuais dentro de cada grupo retornado, use um loop aninhado foreach dentro do loop que itera as chaves de grupo, conforme mostrado no exemplo a seguir.Therefore, to access the individual elements within each returned group, use a nested foreach loop inside the loop that iterates the group keys, as shown in the following example.

class GroupExample1
{
    static void Main()
    {
        // Create a data source.
        string[] words = { "blueberry", "chimpanzee", "abacus", "banana", "apple", "cheese" };

        // Create the query.
        var wordGroups =
            from w in words
            group w by w[0];

        // Execute the query.
        foreach (var wordGroup in wordGroups)
        {
            Console.WriteLine("Words that start with the letter '{0}':", wordGroup.Key);
            foreach (var word in wordGroup)
            {
                Console.WriteLine(word);
            }
        }

        // Keep the console window open in debug mode
        Console.WriteLine("Press any key to exit.");
        Console.ReadKey();
    }
}
/* Output:
      Words that start with the letter 'b':
        blueberry
        banana
      Words that start with the letter 'c':
        chimpanzee
        cheese
      Words that start with the letter 'a':
        abacus
        apple
     */

ExemploExample

Este exemplo mostra como executar a lógica adicional nos grupos depois criá-los, usando uma continuação com into.This example shows how to perform additional logic on the groups after you have created them, by using a continuation with into. Para obter mais informações, consulte into.For more information, see into. O exemplo a seguir consulta cada grupo para selecionar apenas aqueles cujo valor da chave é uma vogal.The following example queries each group to select only those whose key value is a vowel.

class GroupClauseExample2
{
    static void Main()
    {
        // Create the data source.
        string[] words2 = { "blueberry", "chimpanzee", "abacus", "banana", "apple", "cheese", "elephant", "umbrella", "anteater" };

        // Create the query.
        var wordGroups2 =
            from w in words2
            group w by w[0] into grps
            where (grps.Key == 'a' || grps.Key == 'e' || grps.Key == 'i'
                   || grps.Key == 'o' || grps.Key == 'u')
            select grps;

        // Execute the query.
        foreach (var wordGroup in wordGroups2)
        {
            Console.WriteLine("Groups that start with a vowel: {0}", wordGroup.Key);
            foreach (var word in wordGroup)
            {
                Console.WriteLine("   {0}", word);
            }
        }

        // Keep the console window open in debug mode
        Console.WriteLine("Press any key to exit.");
        Console.ReadKey();
    }
}
/* Output:
    Groups that start with a vowel: a
        abacus
        apple
        anteater
    Groups that start with a vowel: e
        elephant
    Groups that start with a vowel: u
        umbrella
*/

ComentáriosRemarks

No tempo de compilação, as cláusulas group são convertidas em chamadas para o método GroupBy.At compile time, group clauses are translated into calls to the GroupBy method.

Confira tambémSee also