group-Klausel (C#-Referenz)

Aktualisiert: November 2007

Die group-Klausel gibt eine Sequenz von IGrouping<TKey, TElement>-Objekten zurück, die Null oder mehr Elemente enthält, die dem Schlüsselwert für die Gruppe entsprechen. Zum Beispiel können Sie eine Sequenz von Zeichenfolgen nach dem ersten Buchstaben in jeder Zeichenfolge gruppieren. In diesem Fall ist der erste Buchstabe der Schlüssel und weist den Typ char auf. Er wird in der Key-Eigenschaft jedes IGrouping<TKey, TElement>-Objekts gespeichert. Der Compiler leitet den Typ des Schlüssels ab.

Sie können einen Abfrageausdruck mit einer group-Klausel beenden, wie im folgenden Beispiel gezeigt:

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

Wenn Sie zusätzliche Abfrageoperationen für jede Gruppe durchführen möchten, können Sie einen temporären Bezeichner festlegen, indem Sie das into-Kontextschlüsselwort verwenden. Wenn Sie into verwenden, müssen Sie mit der Abfrage fortfahren und sie letztendlich entweder mit einer select-Anweisung oder einer anderen group-Klausel wie im folgenden Auszug gezeigt beenden:

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

Ausführlichere Beispiele zur Verwendung von group mit und ohne into finden Sie im Beispielabschnitt in diesem Thema.

Auflisten der Ergebnisse einer Gruppenabfrage

Da die IGrouping<TKey, TElement>-Objekte, die durch eine group-Abfrage erzeugt wurden, im Grunde eine Liste mit Listen sind, müssen Sie eine geschachtelte foreach-Schleife verwenden, um auf die Elemente in jeder Gruppe zuzugreifen. Die äußere Schleife durchläuft die Gruppenschlüssel, und die innere Schleife durchläuft die Elemente in der Gruppe. Eine Gruppe verfügt möglicherweise über einen Schlüssel, jedoch nicht über Elemente. Die foreach-Schleife, die die Abfrage in den vorherigen Codebeispielen ausführt, sieht folgendermaßen aus:

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

Schlüsseltypen

Gruppenschlüssel können ein beliebiger Typ sein, z. B. eine Zeichenfolge, ein integrierter numerischer Typ oder ein benutzerdefinierter benannter Typ bzw. anonymer Typ.

Gruppieren nach Zeichenfolge

In den vorherigen Codebeispielen wurde ein char verwendet. Ein Zeichenfolgenschlüssel hätte mühelos stattdessen festgelegt werden können, z. B. der vollständige Nachname:

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

Gruppieren nach bool

Im folgenden Beispiel wird die Verwendung eines booleschen Werts für einen Schlüssel gezeigt, um die Ergebnisse in zwei Gruppen zu teilen. Beachten Sie, dass der Wert von einem Unterausdruck in der group-Klausel erzeugt wird.

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

Gruppieren nach numerischem Bereich

Im nächsten Beispiel wird ein Ausdruck verwendet, um numerische Gruppenschlüssel zu erstellen, die einen prozentualen Bereich darstellen. Beachten Sie die Auswahl von let als geeigneten Speicherort, um ein Ergebnis eines Methodenaufrufs zu speichern, sodass Sie die Methode nicht zweimal in der group-Klausel aufrufen müssen. Beachten Sie in der group-Klausel auch, dass der Code überprüft, ob der Student keinen Durchschnitt von Null hat, um eine Nulldivisionsausnahme zu vermeiden. Weitere Informationen zum sicheren Verwenden von Methoden in Abfrageausdrücken finden Sie unter Gewusst wie: Behandeln von Ausnahmen in Abfrageausdrücken (C#-Programmierhandbuch).

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

Gruppieren nach zusammengesetzten Schlüsseln

Verwenden Sie einen zusammengesetzten Schlüssel, wenn Sie Elemente anhand mehr als einem Schlüssel gruppieren möchten. Zusammengesetzte Schlüssel können Sie erstellen, indem Sie einen anonymen oder benannten Typ verwenden, der das Schlüsselelement enthalten soll. Im folgenden Beispiel wird angenommen, dass eine Klasse Person mit Member mit den Namen surname und city deklariert wurde. Die group-Klausel bewirkt, dass eine separate Gruppe für jeden Satz an Personen mit dem gleichen Nachnamen und der gleichen Stadt erstellt werden kann.

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

Verwenden Sie einen benannten Typ, wenn Sie die Abfragevariable an eine andere Methode übergeben müssen. Erstellen Sie eine Sonderklasse mit automatisch implementierten Eigenschaften für die Schlüssel, und überschreiben Sie dann die Equals-Methode und die GetHashCode-Methode. Sie können auch eine Struktur verwenden, sodass es nicht unbedingt erforderlich ist, diese Methoden zu überschreiben. Weitere Informationen finden Sie unter Gewusst wie: Implementieren einer einfachen Klasse mit automatisch implementierten Eigenschaften (C#-Programmierhandbuch) und Gewusst wie: Abfragen von Dateiduplikaten in einer Verzeichnisstruktur (LINQ). Das zuletzt genannte Thema zeigt anhand eines Codebeispiels, wie ein zusammengesetzter Schlüssel mit einem benannten Typ verwendet wird.

Beispiel

Das folgende Beispiel zeigt das Standardmuster zum Sortieren von Quelldaten in Gruppen, wenn keine zusätzliche Abfragelogik auf die Gruppen angewendet wurde. Dies wird als Gruppierung ohne Fortsetzung bezeichnet. Die Elemente in einem Zeichenfolgenarray werden nach ihrem ersten Buchstaben gruppiert. Das Ergebnis der Abfrage ist ein IGrouping<TKey, TElement>-Typ, der eine öffentliche Key-Eigenschaft vom Typ char und eine IEnumerable<T>-Auflistung enthält, die wiederum jedes Element in der Gruppierung umfasst.

Das Ergebnis einer group-Klausel ist eine Sequenz von Sequenzen. Verwenden Sie daher zum Zugriff auf die einzelnen Elemente in jeder zurückgegebenen Gruppe eine geschachtelte foreach-Schleife in der Schleife, die die Gruppenschlüssel wie im folgenden Beispiel gezeigt durchläuft.

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

Dieses Beispiel zeigt, wie Sie zusätzliche Logik auf die Gruppen anwenden können, nachdem Sie sie erstellt haben, indem Sie eine Fortsetzung mit into verwenden. Weitere Informationen finden Sie unter into (C#-Referenz). Im folgenden Beispiel wird jede Gruppe abgefragt, um nur jene auszuwählen, deren Schlüsselwert ein Vokal ist.

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

Hinweise

Zum Zeitpunkt der Kompilierung werden group-Klauseln in Aufrufe der GroupBy-Methode übersetzt.

Siehe auch

Aufgaben

Gewusst wie: Gruppieren einer Gruppe (C#-Programmierhandbuch)

Gewusst wie: Gruppieren von Ergebnissen auf verschiedene Weisen (C#-Programmierhandbuch)

Gewusst wie: Ausführen einer Unterabfrage für eine Gruppierungsoperation (C#-Programmierhandbuch)

Konzepte

LINQ-Abfrageausdrücke (C#-Programmierhandbuch)

Referenz

IGrouping<TKey, TElement>

GroupBy

ThenBy

ThenByDescending

Weitere Ressourcen

Abfrageschlüsselwörter (C#-Referenz)