Condividi tramite


Procedura: raggruppare i risultati in vari modi (Guida per programmatori C#)

Aggiornamento: novembre 2007

Il raggruppamento è una delle funzionalità più potenti di LINQ. Nell'esempio riportato di seguito viene illustrato come raggruppare i dati con diverse modalità:

  • In base a una singola proprietà.

  • In base alla prima lettera di una proprietà di stringa.

  • In base a un intervallo numerico calcolato.

  • In base al predicato booleano o altra espressione.

  • In base a una chiave composta.

Le ultime due query proiettano inoltre i risultati in un tipo anonimo nuovo che contiene solo il nome e il cognome dello studente. Per ulteriori informazioni, vedere Clausola group (Riferimento C#).

Esempio

In tutti gli esempi di questo argomento vengono utilizzate le classi di supporto e le origini dati seguenti.

public class StudentClass
{
    #region data
    protected enum GradeLevel { FirstYear = 1, SecondYear, ThirdYear, FourthYear };
    protected class Student
    {
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public int ID { get; set; }
        public GradeLevel Year;
        public List<int> ExamScores;
    }

    protected static List<Student> students = new List<Student>
    {
        new Student {FirstName = "Terry", LastName = "Adams", ID = 120, Year = GradeLevel.SecondYear, ExamScores = new List<int>{ 99, 82, 81, 79}},
        new Student {FirstName = "Fadi", LastName = "Fakhouri", ID = 116, Year = GradeLevel.ThirdYear,ExamScores = new List<int>{ 99, 86, 90, 94}},
        new Student {FirstName = "Hanying", LastName = "Feng", ID = 117, Year = GradeLevel.FirstYear, ExamScores = new List<int>{ 93, 92, 80, 87}},
        new Student {FirstName = "Cesar", LastName = "Garcia", ID = 114, Year = GradeLevel.FourthYear,ExamScores = new List<int>{ 97, 89, 85, 82}},
        new Student {FirstName = "Debra", LastName = "Garcia", ID = 115, Year = GradeLevel.ThirdYear, ExamScores = new List<int>{ 35, 72, 91, 70}},
        new Student {FirstName = "Hugo", LastName = "Garcia", ID = 118, Year = GradeLevel.SecondYear, ExamScores = new List<int>{ 92, 90, 83, 78}},
        new Student {FirstName = "Sven", LastName = "Mortensen", ID = 113, Year = GradeLevel.FirstYear, ExamScores = new List<int>{ 88, 94, 65, 91}},
        new Student {FirstName = "Claire", LastName = "O'Donnell", ID = 112, Year = GradeLevel.FourthYear, ExamScores = new List<int>{ 75, 84, 91, 39}},
        new Student {FirstName = "Svetlana", LastName = "Omelchenko", ID = 111, Year = GradeLevel.SecondYear, ExamScores = new List<int>{ 97, 92, 81, 60}},
        new Student {FirstName = "Lance", LastName = "Tucker", ID = 119, Year = GradeLevel.ThirdYear, ExamScores = new List<int>{ 68, 79, 88, 92}},
        new Student {FirstName = "Michael", LastName = "Tucker", ID = 122, Year = GradeLevel.FirstYear, ExamScores = new List<int>{ 94, 92, 91, 91}},
        new Student {FirstName = "Eugene", LastName = "Zabokritski", ID = 121, Year = GradeLevel.FourthYear, ExamScores = new List<int>{ 96, 85, 91, 60}}
    };
    #endregion

    //Helper method
    protected static int GetPercentile(Student s)
    {
        double avg = s.ExamScores.Average();
        return avg > 0 ? (int)avg / 10 : 0;
    }



    public void QueryHighScores(int exam, int score)
    {
        var highScores = from student in students
                         where student.ExamScores[exam] > score
                         select new {Name = student.FirstName, Score = student.ExamScores[exam]};

        foreach (var item in highScores)
        {
            Console.WriteLine("{0,-15}{1}", item.Name, item.Score);
        }
    }
}

public class Program
{
    public static void Main()
    {
        StudentClass sc = new StudentClass();
        sc.QueryHighScores(1, 90);

        // Keep the console window open in debug mode
        Console.WriteLine("Press any key to exit");
        Console.ReadKey();
    }
}

Nell'esempio seguente viene illustrato come raggruppare elementi di origine utilizzando una sola proprietà dell'elemento come chiave di gruppo. In tal caso, la chiave è un oggetto string. È anche possibile utilizzare una sottostringa per la chiave. L'operazione di raggruppamento utilizza l'operatore di confronto di uguaglianza predefinito per il tipo.

private static void GroupBySingleProperty()
{
    Console.WriteLine("Group by a single property in an object");

    // queryLastNames is an IEnumerable<IGrouping<string, DataClass.Student>>
    // var is easier to type.
    var queryLastNames =
        from student in students
        group student by student.LastName into newGroup
        orderby newGroup.Key
        select newGroup;

    foreach (var nameGroup in queryLastNames)
    {
        Console.WriteLine("Key: {0}", nameGroup.Key);
        foreach (var student in nameGroup)
        {
            Console.WriteLine("\t{0}, {1}", student.LastName, student.FirstName);
        }
    }
}
/* Output:
  Group by a single property in an object
  Key: Feng
          Feng, Hanying
  Key: Garcia
          Garcia, Hugo
          Garcia, Cesar
          Garcia, Debra
  Key: Mortensen
          Mortensen, Sven
  Key: O'Donnell
          O'Donnell, Claire
  Key: Omelchenko
          Omelchenko, Svetlana
  Key: Tucker
          Tucker, Michael
          Tucker, Lance
 */  

Nell'esempio seguente viene illustrato come raggruppare elementi di origine utilizzando uno strumento diverso da una proprietà dell'oggetto per la chiave di gruppo.

private static void GroupBySubstring()
{            
    Console.WriteLine("\r\nGroup by something other than a property of the object:");

    var queryFirstLetters =
        from student in students
        group student by student.LastName[0];

    foreach (var studentGroup in queryFirstLetters)
    {
        Console.WriteLine("Key: {0}", studentGroup.Key);
        // Nested foreach is required to access group items
        foreach (var student in studentGroup)
        {
            Console.WriteLine("\t{0}, {1}", student.LastName, student.FirstName);
        }
    }           
}
/* Output:
        Group by first character:
        Key: O
                Omelchenko, Svetlana
                O'Donnell, Claire
        Key: G
                Garcia, Hugo
                Garcia, Cesar
                Garcia, Debra
        Key: M
                Mortensen, Sven
        Key: T
                Tucker, Michael
                Tucker, Lance
        Key: F
                Feng, Hanying
     */

Nell'esempio seguente viene illustrato come raggruppare elementi di origine utilizzando un intervallo numerico come chiave di gruppo. La query proietta quindi i risultati in un tipo anonimo che contiene solo il nome e il cognome e l'intervallo percentile al quale appartiene lo studente. Viene utilizzato un tipo anonimo perché non è necessario utilizzare l'oggetto Student completo per visualizzare i risultati. GetPercentile è una funzione di supporto che calcola un percentile basato sul punteggio medio dello studente:

static int GetPercentile(Student s)
{
   double avg = s.Scores.Average();
   return avg > 0 ? (int)avg / 10 : 0;
}
private static void GroupByRange()
{            
    Console.WriteLine("\r\nGroup by numeric range and project into a new anonymous type:");

    var queryNumericRange =
        from student in students
        let percentile = GetPercentile(student)
        group new { student.FirstName, student.LastName } by percentile into percentGroup
        orderby percentGroup.Key
        select percentGroup;

    // Nested foreach required to iterate over groups and group items.
    foreach (var studentGroup in queryNumericRange)
    {
        Console.WriteLine("Key: {0}", (studentGroup.Key * 10));
        foreach (var item in studentGroup)
        {
            Console.WriteLine("\t{0}, {1}", item.LastName, item.FirstName);
        }
    }            
}
/* Output:
     Group by numeric range and project into a new anonymous type:
     Key: 60
             Garcia, Debra
     Key: 70
             Omelchenko, Svetlana
             O'Donnell, Claire
     Key: 80
             Garcia, Hugo
             Mortensen, Sven
             Garcia, Cesar
             Feng, Hanying
             Tucker, Lance
     Key: 90
             Tucker, Michael
     */

Nell'esempio seguente viene illustrato come raggruppare elementi di origine utilizzando un'espressione di confronto booleana. Come negli esempi precedenti, i risultati vengono proiettati in un tipo anonimo perché l'elemento di origine completo non è necessario. Si noti che le proprietà nel tipo anonimo diventano proprietà sul membro Key e sono accessibili tramite il nome quando la query viene eseguita.

private static void GroupByBoolean()
{            
    Console.WriteLine("\r\nGroup by a boolean into two groups with string keys");
    Console.WriteLine("\"True\" and \"False\" and project into a new anonymous type:");
    var queryGroupByAverages = from student in students
                               group new { student.FirstName, student.LastName }
                                    by student.ExamScores.Average() > 75 into studentGroup
                               select studentGroup;

    foreach (var studentGroup in queryGroupByAverages)
    {
        Console.WriteLine("Key: {0}", studentGroup.Key);
        foreach (var student in studentGroup)
            Console.WriteLine("\t{0} {1}", student.FirstName, student.LastName);
    }            
}
/* Output:
         Group by a boolean into two groups with string keys
         "True" and "False" and project into a new anonymous type:
         Key: True
                 Svetlana Omelchenko
                 Hugo Garcia
                 Sven Mortensen
                 Michael Tucker
                 Cesar Garcia
                 Hanying Feng
                 Lance Tucker
         Key: False
                 Claire O'Donnell
                 Debra Garcia
*/

Nell'esempio seguente viene illustrato come utilizzare un tipo anonimo per incapsulare una chiave che contiene più valori. In questo caso, il secondo valore della chiave è booleano e specifica se lo studente ha ottenuto un voto superiore a 85 nel primo esame. È possibile ordinare i gruppi in base a qualsiasi proprietà nella chiave.

private static void GroupByCompositeKey()
{

    var queryHighScoreGroups =
        from student in students
        group student by new { FirstLetter = student.LastName[0], Score = student.ExamScores[0] > 85 } into studentGroup
        orderby studentGroup.Key.FirstLetter
        select studentGroup;

    Console.WriteLine("\r\nGroup and order by a compound key:");
    foreach (var scoreGroup in queryHighScoreGroups)
    {
        string s = scoreGroup.Key.Score == true ? "more than" : "less than";
        Console.WriteLine("Name starts with {0} who scored {1} 85", scoreGroup.Key.FirstLetter, s);
        foreach (var item in scoreGroup)
        {
            Console.WriteLine("\t{0} {1}", item.FirstName, item.LastName);
        }
    }
}
/* Output:
          Group and order by a compound key:
          Name starts with F who scored more than 85
                  Hanying Feng
          Name starts with G who scored more than 85
                  Hugo Garcia
                  Cesar Garcia
          Name starts with G who scored less than 85
                  Debra Garcia
          Name starts with M who scored more than 85
                  Sven Mortensen
          Name starts with O who scored more than 85
                  Svetlana Omelchenko
          Name starts with O who scored less than 85
                  Claire O'Donnell
          Name starts with T who scored more than 85
                  Michael Tucker
          Name starts with T who scored less than 85
                  Lance Tucker
       */

Compilazione del codice

In questo esempio sono contenuti i riferimenti a oggetti definiti nell'applicazione di esempio in Procedura: eseguire una query su un insieme di oggetti (Guida per programmatori C#). Per compilare ed eseguire questo metodo, incollarlo nella classe StudentClass in tale applicazione e aggiungervi una chiamata dal metodo Main.

When you adapt this method to your own application, remember that LINQ requires version 3.5 of the .NET Framework, and the project must contain a reference to System.Core.dll and a using directive for System.Linq. LINQ to SQL, LINQ to XML and LINQ to DataSet types require additional usings and references. Per ulteriori informazioni, vedere la classe Procedura: creare un progetto LINQ.

Vedere anche

Attività

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

Procedura: raggruppare un gruppo (Guida per programmatori C#)

Concetti

Espressioni query LINQ (Guida per programmatori C#)

Raggruppamento dei dati

Riferimenti

Clausola group (Riferimento C#)

Tipi anonimi (Guida per programmatori C#)

GroupBy

IGrouping<TKey, TElement>