Condividi tramite


Clausola `group` (Riferimento C#)

La group clausola restituisce una sequenza di IGrouping<TKey,TElement> oggetti. La sequenza contiene zero o più elementi che corrispondono al valore della chiave per il gruppo. Ad esempio, è possibile raggruppare una sequenza di stringhe in base alla prima lettera di ogni stringa. In questo caso, la prima lettera è la chiave e ha un tipo char. Ogni IGrouping<TKey,TElement> oggetto archivia questa chiave nella relativa Key proprietà. Il compilatore deduce automaticamente il tipo della chiave.

Il riferimento al linguaggio C# documenta la versione rilasciata più di recente del linguaggio C#. Contiene anche la documentazione iniziale per le funzionalità nelle versioni di anteprima pubblica per la prossima versione del linguaggio di programmazione.

La documentazione identifica tutte le funzionalità introdotte nelle ultime tre versioni della lingua o nelle anteprime pubbliche correnti.

Suggerimento

Per trovare quando una funzionalità è stata introdotta per la prima volta in C#, vedere l'articolo sulla cronologia delle versioni del linguaggio C#.

È 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];

Per eseguire operazioni di query aggiuntive su ogni gruppo, specificare un identificatore temporaneo usando la parola chiave contestuale in . Quando si usa into, è necessario continuare a eseguire la query ed eventualmente terminarla con un'istruzione select o un'altra clausola group, come illustrato nel seguente estratto di codice:

// 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 di utilizzo di più completi di group con e senza into sono disponibili nella sezione Esempio di questo articolo.

Enumerazione dei risultati di una query di gruppo

Poiché gli IGrouping<TKey,TElement> oggetti prodotti da una group query sono essenzialmente un elenco di elenchi, è necessario utilizzare un ciclo foreach annidato per accedere agli elementi in ogni gruppo. Il ciclo esterno itera sulle chiavi del gruppo e il ciclo interno itera su ogni elemento nel gruppo. Un gruppo può avere una chiave, ma nessun elemento. Il ciclo seguente foreach 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 raggruppamento possono essere di qualsiasi tipo, ad esempio una stringa, un tipo numerico incorporato, un tipo con nome definito dall'utente o un tipo anonimo.

Raggruppamento per stringa

Negli esempi di codice precedenti è stato usato un oggetto char. È anche possibile specificare 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 valore booleano

Nell'esempio seguente viene usato un valore bool per una chiave per dividere i risultati in due gruppi. Il valore proviene da una sottoespressione nella group clausola .

class GroupSample1
{
    // The element type of the data source.
    public class Student
    {
        public required string First { get; init; }
        public required string Last { get; init; }
        public required int ID { get; init; }
        public required 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 Student {First="Svetlana", Last="Omelchenko", ID=111, Scores= [97, 72, 81, 60]},
           new Student {First="Claire", Last="O'Donnell", ID=112, Scores= [75, 84, 91, 39]},
           new Student {First="Sven", Last="Mortensen", ID=113, Scores= [99, 89, 91, 95]},
           new Student {First="Cesar", Last="Garcia", ID=114, Scores= [72, 81, 65, 84]},
           new Student {First="Debra", Last="Garcia", ID=115, Scores= [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());
            }
        }
    }
}
/* 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 seguente viene usata un'espressione per creare le chiavi di raggruppamento numeriche che rappresentano un intervallo percentile. Viene usato let per archiviare un risultato della chiamata al metodo, pertanto non è necessario chiamare il metodo due volte nella group clausola . Per altre informazioni su come usare in modo sicuro i metodi nelle espressioni di query, vedere Gestire le eccezioni nelle espressioni di query.

class GroupSample2
{
    // The element type of the data source.
    public class Student
    {
        public required string First { get; init; }
        public required string Last { get; init; }
        public required int ID { get; init; }
        public required 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 Student {First="Svetlana", Last="Omelchenko", ID=111, Scores= [97, 72, 81, 60]},
           new Student {First="Claire", Last="O'Donnell", ID=112, Scores= [75, 84, 91, 39]},
           new Student {First="Sven", Last="Mortensen", ID=113, Scores= [99, 89, 91, 95]},
           new Student {First="Cesar", Last="Garcia", ID=114, Scores= [72, 81, 65, 84]},
           new Student {First="Debra", Last="Garcia", ID=115, Scores= [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 {temp} and {temp + 10}");
            foreach (var student in studentGroup)
            {
                Console.WriteLine("   {0}, {1}:{2}", student.Last, student.First, student.Scores.Average());
            }
        }
    }
}
/* 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

Usare una chiave composita quando si desidera raggruppare gli elementi in base a più di una chiave. Creare una chiave composita usando un tipo anonimo o un tipo denominato per contenere l'elemento chiave. Nell'esempio seguente si supponga che una classe Person abbia membri denominati surname e city. La group clausola crea un gruppo separato per ogni set di persone con lo stesso cognome e la stessa città.

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

Usare un tipo denominato se è necessario passare la variabile di query a un altro metodo. Creare una classe speciale con proprietà implementate automaticamente per le chiavi e quindi eseguire l'override dei Equals metodi e GetHashCode . È anche possibile usare uno struct, che non richiede rigorosamente tali override del metodo. Per altre informazioni, vedere Come implementare una classe leggera con proprietà implementate automaticamente e Come eseguire query per i file duplicati in un albero di directory. Il secondo articolo contiene un esempio di codice che illustra come usare una chiave composta con un tipo denominato.

Esempi

L'esempio seguente illustra il modello standard per ordinare i dati di origine in gruppi quando non si applica alcuna logica di query aggiuntiva ai gruppi. Questo modello viene chiamato raggruppamento senza continuazione. L'esempio raggruppa gli elementi in una matrice di stringhe in base alla prima lettera. Il risultato della query è un tipo IGrouping<TKey,TElement> che contiene una proprietà pubblica Key di tipo char e una raccolta IEnumerable<T> che contiene ogni elemento nel raggruppamento.

Il risultato di una clausola group è una sequenza di sequenze. Per accedere ai singoli elementi all'interno di ogni gruppo restituito, usare un ciclo annidato foreach all'interno del ciclo che esegue l'iterazione delle 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 '{wordGroup.Key}':");
            foreach (var word in wordGroup)
            {
                Console.WriteLine(word);
            }
        }
    }
}
/* 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
     */

Nell'esempio seguente viene illustrato come eseguire una logica aggiuntiva sui gruppi dopo averli creati usando una continuazione con into. Per altre informazioni, vedere into. Nell'esempio seguente viene eseguita una query di 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: {wordGroup.Key}");
            foreach (var word in wordGroup)
            {
                Console.WriteLine($"   {word}");
            }
        }
    }
}
/* 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
*/

In fase di compilazione, il compilatore converte le group clausole in chiamate al GroupBy metodo .

La sintassi della query della group clausola non supporta l'operatore di confronto di uguaglianza personalizzato. Se si vuole usare IEqualityComparer nella query, usare in modo esplicito il GroupBy metodo .

Vedi anche