Udostępnij za pośrednictwem


klauzula group (odwołanie 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, używając kontekstowego słowa kluczowego into. Jeśli używasz into, musisz kontynuować zapytanie i ostatecznie zakończyć je instrukcją select lub inną klauzulą group, jak pokazano na poniższym fragmencie.

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

Kompletne przykłady użycia z elementem group i bez into znajdują się w sekcji Przykłady 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 po kluczach grupy, a pętla wewnętrzna iteruje po elementach w samej grupie. Grupa może mieć klucz, ale nie mieć ż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, typ nazwany zdefiniowany przez użytkownika czy 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 (bool) jako klucza do podzielenia wyników na dwie grupy. Należy pamiętać, że wartość jest generowana 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 {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
 */

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 specjalną klasę, używając automatycznie zaimplementowanych właściwości dla kluczy, a następnie przesłoń metody Equals 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 '{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, korzystając z kontynuacji przy 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: {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
*/

Uwagi

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

Niestandardowy porównywacz równości nie jest obsługiwany w składni zapytania w klauzuli group. Użyj GroupBy metody jawnie, jeśli chcesz użyć IEqualityComparer w zapytaniu.

Zobacz też