group — Klauzula (odwołanie w C#)

Klauzula group zwraca sekwencję IGrouping<TKey,TElement> obiektów, które zawierają zero lub więcej elementów pasujących do wartości klucza dla grupy. Można na przykład zgrupować sekwencję ciągów zgodnie z pierwszą literą w każdym ciągu. W tym przypadku pierwsza litera jest kluczem i ma typ char i jest przechowywana we Key właściwości każdego IGrouping<TKey,TElement> obiektu. Kompilator wywnioskuje typ klucza.

Wyrażenie zapytania można zakończyć za pomocą klauzuli group , jak pokazano w poniższym przykładzie:

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

Jeśli chcesz wykonać dodatkowe operacje zapytań dla każdej grupy, możesz określić identyfikator tymczasowy przy użyciu elementu w kontekstowym słowie kluczowym. Jeśli używasz metody into, musisz kontynuować wykonywanie zapytania i ostatecznie zakończyć je instrukcją lub inną group klauzuląselect, jak pokazano na poniższym fragcie:

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

Bardziej kompletne przykłady użycia z elementem group i bez into znajdują się w sekcji Przykład w tym artykule.

Wyliczanie wyników zapytania grupy

IGrouping<TKey,TElement> Ponieważ obiekty tworzone przez group zapytanie są zasadniczo listą list, należy użyć zagnieżdżonej pętli foreach, aby uzyskać dostęp do elementów w każdej grupie. Pętla zewnętrzna iteruje klucze grupy, a pętla wewnętrzna iteruje każdy element w samej grupie. Grupa może mieć klucz, ale nie ma żadnych elementów. Poniżej znajduje się pętla foreach , która wykonuje zapytanie w poprzednich przykładach kodu:

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

Typy kluczy

Klucze grupy mogą być dowolnym typem, takim jak ciąg, wbudowany typ liczbowy lub typ nazwany zdefiniowany przez użytkownika lub typ anonimowy.

Grupowanie według ciągu

W poprzednich przykładach kodu użyto elementu char. Zamiast tego można było łatwo określić klucz ciągu, na przykład pełne nazwisko:

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

Grupowanie według wartości logicznej

W poniższym przykładzie pokazano użycie wartości logicznej klucza w celu podzielenia wyników na dwie grupy. Należy pamiętać, że wartość jest generowany przez wyrażenie podrzędne w klauzuli group .

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

Grupowanie według zakresu liczbowego

W następnym przykładzie użyto wyrażenia do utworzenia liczbowych kluczy grup reprezentujących zakres percentylu. Zwróć uwagę na użycie funkcji let jako wygodnej lokalizacji do przechowywania wyniku wywołania metody, aby nie trzeba było wywołać metody dwa razy w klauzuli group . Aby uzyskać więcej informacji na temat bezpiecznego używania metod w wyrażeniach zapytań, zobacz Obsługa wyjątków w wyrażeniach zapytań.

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 {0} and {1}", temp, 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
 */

Grupowanie według kluczy złożonych

Użyj klucza złożonego, jeśli chcesz zgrupować elementy według więcej niż jednego klucza. Klucz złożony tworzy się przy użyciu typu anonimowego lub nazwanego typu do przechowywania elementu klucza. W poniższym przykładzie przyjęto założenie, że klasa Person została zadeklarowana przy użyciu elementów członkowskich o nazwach surname i city. Klauzula group powoduje utworzenie oddzielnej grupy dla każdego zestawu osób o tym samym imieniu i tym samym mieście.

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

Użyj nazwanego typu, jeśli musisz przekazać zmienną kwerendy do innej metody. Utwórz klasę specjalną przy użyciu właściwości implementowanych automatycznie dla kluczy, a następnie przesłoń Equals metody i GetHashCode . Można również użyć struktury, w tym przypadku nie trzeba ściśle zastępować tych metod. Aby uzyskać więcej informacji, zobacz Jak zaimplementować lekką klasę z automatycznie zaimplementowanymi właściwościami i Jak wykonywać zapytania dotyczące zduplikowanych plików w drzewie katalogów. W tym drugim artykule przedstawiono przykład kodu, który pokazuje, jak używać klucza złożonego z nazwanym typem.

Przykład 1

W poniższym przykładzie przedstawiono standardowy wzorzec porządkowania danych źródłowych w grupach, gdy do grup nie jest stosowana żadna dodatkowa logika zapytania. Jest to nazywane grupowaniem bez kontynuacji. Elementy w tablicy ciągów są grupowane zgodnie z ich pierwszą literą. Wynikiem zapytania jest IGrouping<TKey,TElement> typ zawierający właściwość publiczną Key typu char i IEnumerable<T> kolekcję zawierającą każdy element w grupowaniu.

Wynikiem klauzuli group jest sekwencja sekwencji. W związku z tym, aby uzyskać dostęp do poszczególnych elementów w każdej zwróconej grupie, użyj zagnieżdżonej foreach pętli wewnątrz pętli, która iteruje klucze grupy, jak pokazano w poniższym przykładzie.

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

Przykład 2

W tym przykładzie pokazano, jak wykonać dodatkową logikę dla grup po ich utworzeniu przy użyciu kontynuacji z into. Aby uzyskać więcej informacji, zobacz. Poniższy przykład wysyła zapytanie do każdej grupy, aby wybrać tylko te, których wartość klucza jest samogłoską.

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

Uwagi

W czasie group kompilacji klauzule są tłumaczone na wywołania GroupBy metody .

Zobacz też