Clausola group (Riferimento C#)

La clausola group restituisce una sequenza di oggetti IGrouping<TKey, TElement> che contengono zero o più elementi corrispondenti al valore della chiave per il gruppo. È possibile, ad esempio, raggruppare una sequenza di stringhe in base alla prima lettera di ogni stringa. In questo caso, la prima lettera è la chiave e dispone di un tipo char; inoltre è memorizzata nella proprietà Key di ogni oggetto IGrouping<TKey, TElement>. Il compilatore deduce il tipo della chiave.

È possibile terminare un'espressione di query con una clausola group, come illustrato nell'esempio seguente:

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

Se si desidera eseguire operazioni di query aggiuntive in ogni gruppo, è possibile specificare un identificatore temporaneo utilizzando la parola chiave contestuale into. Quando si utilizza into, è necessario continuare con la query ed eventualmente terminarla con un'istruzione select o un'altra clausola group, come illustrato nell'estratto seguente:

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

Esempi più completi relativi all'utilizzo dell'oggetto group con e senza l'oggetto into sono disponibili nella sezione relativa agli esempi di questo argomento.

Enumerazione dei risultati di una query group

Poiché gli oggetti IGrouping<TKey, TElement> prodotti da una query group sono essenzialmente un elenco di elenchi, è necessario utilizzare un ciclo foreach annidato per accedere agli elementi in ogni gruppo. Il ciclo esterno scorre le chiavi di gruppo e il ciclo interno scorre ogni elemento nel gruppo stesso. È possibile che un gruppo contenga una chiave, ma nessun elemento. Di seguito viene riportato il ciclo foreach che esegue la query negli esempi di codice precedenti:

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

Tipi di chiave

Le chiavi di gruppo possono essere qualsiasi tipo, ad esempio una stringa, un tipo numerico incorporato oppure un tipo denominato definito dall'utente o un tipo anonimo.

Raggruppamento per stringhe

Negli esempi di codice precedenti viene utilizzato char. Sarebbe stato invece possibile specificare facilmente una chiave stringa, ad esempio il cognome completo:

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

Raggruppamento per bool

Nell'esempio seguente viene illustrato l'utilizzo di un valore booleano per fare in modo che una chiave divida i risultati in due gruppi. Si noti che il valore viene prodotto da una sottoespressione nella clausola group.

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

Raggruppamento per intervallo numerico

Nell'esempio successivo viene utilizzata un'espressione per creare chiavi di gruppo numeriche che rappresentano un intervallo percentile. Si noti l'utilizzo di let come percorso appropriato per archiviare un risultato di chiamata al metodo, in modo che non sia necessario chiamare il metodo due volte nella clausola group. Si noti inoltre che nella clausola group, per evitare un'eccezione che comporta la divisione per zero, il codice verifica che la media dello studente non sia pari a zero. Per ulteriori informazioni sull'utilizzo sicuro dei metodi nelle espressioni di query, vedere Procedura: gestire le eccezioni nelle espressioni di query (Guida per programmatori C#).

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 == 0 ? 0 : 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
 */

Raggruppamento per chiavi composte

Utilizzare una chiave composta quando si desidera raggruppare elementi in base a più di una chiave. Si crea una chiave composta utilizzando un tipo anonimo o un tipo denominato per contenere l'elemento chiave. Nell'esempio seguente, si supponga che una classe Person sia stata dichiarata con i membri denominati surname e city. La clausola group fa in modo che venga creato un gruppo distinto per ogni insieme di persone con lo stesso cognome e la stessa città.

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

Utilizzare un tipo denominato se è necessario passare la variabile della query a un altro metodo. Creare una classe speciale utilizzando proprietà implementate automaticamente per le chiavi e quindi eseguire l'override dei metodi Equals e GetHashCode. È anche possibile utilizzare una struttura, nel qual caso non è indispensabile eseguire l'override di tali metodi. Per ulteriori informazioni, vedere Procedura: implementare una classe leggera con proprietà implementate automaticamente (Guida per programmatori C#) e Procedura: eseguire una query per trovare i file duplicati in una struttura ad albero di directory (LINQ). Nel secondo argomento è disponibile un esempio di codice che dimostra come utilizzare una chiave composta con un tipo denominato.

Esempio

Nell'esempio seguente viene illustrato il modello standard per l'ordinamento di dati di origine in gruppi quando non è applicata alcuna logica della query aggiuntiva ai gruppi. Si tratta di un raggruppamento senza continuazione. Gli elementi in una matrice di stringhe vengono raggruppati in base alla prima lettera. Il risultato della query è un tipo IGrouping<TKey, TElement> che contiene una proprietà Key pubblica di tipo char e un insieme IEnumerable<T> che contiene ogni elemento nel raggruppamento.

Il risultato di una clausola group è una sequenza di sequenze. Pertanto, per accedere ai singoli elementi all'interno di ogni gruppo restituito, utilizzare un ciclo foreach annidato all'interno del ciclo che scorre le chiavi di gruppo, come illustrato nell'esempio seguente.

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

In questo esempio viene illustrato come eseguire la logica aggiuntiva nei gruppi dopo averli creati, utilizzando una continuazione con into. Per ulteriori informazioni, vedere into (Riferimenti per C#). Nell'esempio seguente viene eseguita una query su ogni gruppo per selezionare solo quelli il cui valore della chiave è una vocale.

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

Note

In fase di compilazione, le clausole group vengono convertite in chiamate al metodo GroupBy.

Vedere anche

Attività

Procedura: creare un gruppo annidato (Guida per programmatori C#)

Procedura: raggruppare i risultati di una query (Guida per programmatori C#)

Procedura: eseguire una sottoquery su un'operazione di raggruppamento (Guida per programmatori C#)

Riferimenti

IGrouping<TKey, TElement>

GroupBy

ThenBy

ThenByDescending

Concetti

Espressioni di query LINQ (Guida per programmatori C#)

Altre risorse

Parole chiave di query (Riferimenti per C#)