group 子句 (C# 參考)group clause (C# Reference)

group 子句會傳回一系列的 IGrouping<TKey,TElement> 物件,而這些物件包含符合群組之索引鍵值的零或多個項目。The group clause returns a sequence of IGrouping<TKey,TElement> objects that contain zero or more items that match the key value for the group. 例如,您可以根據每個字串中的第一個字母來分組一序列的字串。For example, you can group a sequence of strings according to the first letter in each string. 在此情況下,第一個字母是索引鍵、具有類型 char,並儲存在每個 IGrouping<TKey,TElement> 物件的 Key 屬性中。In this case, the first letter is the key and has a type char, and is stored in the Key property of each IGrouping<TKey,TElement> object. 編譯器會推斷索引鍵類型。The compiler infers the type of the key.

您可以使用 group 子句結束查詢運算式,如下列範例所示︰You can end a query expression with a group clause, as shown in the following example:

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

如果您想要對每個群組執行其他查詢作業,則可以使用 into 內容關鍵字來指定暫時識別碼。If you want to perform additional query operations on each group, you can specify a temporary identifier by using the into contextual keyword. 當您使用 into 時,必須繼續進行查詢,最後以 select 陳述式或另一個 group 子句結束,如下列摘錄所示︰When you use into, you must continue with the query, and eventually end it with either a select statement or another group clause, as shown in the following excerpt:

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

本文的<範例>一節中提供使用或未使用 intogroup 的更完整範例。More complete examples of the use of group with and without into are provided in the Example section of this article.

列舉群組查詢結果Enumerating the results of a group query

因為 group 查詢所產生的 IGrouping<TKey,TElement> 物件基本上是清單的清單,所以您必須使用巢狀 foreach 迴圈來存取每個群組中的項目。Because the IGrouping<TKey,TElement> objects produced by a group query are essentially a list of lists, you must use a nested foreach loop to access the items in each group. 外部迴圈會逐一查看群組索引鍵,內部迴圈則會逐一查看群組本身中的每個項目。The outer loop iterates over the group keys, and the inner loop iterates over each item in the group itself. 群組可能具有索引鍵,但沒有項目。A group may have a key but no elements. 下列 foreach 迴圈會執行先前程式碼範例中的查詢︰The following is the foreach loop that executes the query in the previous code examples:

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

索引鍵類型Key types

群組索引鍵可以是任何類型,例如字串、內建數值類型,或使用者定義的具名類型或匿名型別。Group keys can be any type, such as a string, a built-in numeric type, or a user-defined named type or anonymous type.

依字串群組Grouping by string

先前的程式碼範例已使用 charThe previous code examples used a char. 可以改為輕鬆地指定字串索引鍵,例如完整姓氏︰A string key could easily have been specified instead, for example the complete last name:

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

依 bool 群組Grouping by bool

下列範例示範如何使用索引鍵的 bool 值,以將結果分成兩個群組。The following example shows the use of a bool value for a key to divide the results into two groups. 請注意,值是由 group 子句中的子運算式所產生。Note that the value is produced by a sub-expression in the group clause.

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

依數字範圍群組Grouping by numeric range

下一個範例使用運算式來建立代表百分位數範圍的數字群組索引鍵。The next example uses an expression to create numeric group keys that represent a percentile range. 請注意會使用 let 作為儲存方法呼叫結果的方便位置,因此不需要在 group 子句中呼叫方法兩次。Note the use of let as a convenient location to store a method call result, so that you don't have to call the method two times in the group clause. 如需如何在查詢運算式中安全地使用方法的詳細資訊,請參閱如何:處理查詢運算式中的例外狀況For more information about how to safely use methods in query expressions, see How to: Handle Exceptions in Query Expressions.

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

依複合索引鍵群組Grouping by composite keys

當您想要根據多個索引鍵來群組項目時,請使用複合索引鍵。Use a composite key when you want to group elements according to more than one key. 您可以使用匿名型別或具名類型來保存索引鍵項目,以建立複合索引鍵。You create a composite key by using an anonymous type or a named type to hold the key element. 在下列範例中,假設已宣告 Person 類別具有名為 surnamecity 的成員。In the following example, assume that a class Person has been declared with members named surname and city. group 子句會為每一組具有相同姓氏和相同城市的人員,建立個別群組。The group clause causes a separate group to be created for each set of persons with the same last name and the same city.

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

如果您必須將查詢變數傳遞給另一種方法,請使用具名類型。Use a named type if you must pass the query variable to another method. 請使用索引鍵的自動實作屬性建立特殊類別,然後覆寫 EqualsGetHashCode 方法。Create a special class using auto-implemented properties for the keys, and then override the Equals and GetHashCode methods. 您也可以使用結構,在此情況下,您絕對不需要覆寫這些方法。You can also use a struct, in which case you do not strictly have to override those methods. 如需詳細資訊,請參閱如何:使用自動實作的屬性來實作輕量型類別如何:查詢樹狀目錄中的重複檔案For more information see How to: Implement a Lightweight Class with Auto-Implemented Properties and How to: Query for Duplicate Files in a Directory Tree. 第二篇文章的程式碼範例示範如何使用含有具名類型的複合索引鍵。The latter article has a code example that demonstrates how to use a composite key with a named type.

範例Example

下列範例示範未將任何其他查詢邏輯套用到群組時,將來源資料排序成群組的標準模式。The following example shows the standard pattern for ordering source data into groups when no additional query logic is applied to the groups. 這稱為無接續群組。This is called a grouping without a continuation. 字串陣列中的項目是根據其第一個字母進行分組。The elements in an array of strings are grouped according to their first letter. 查詢的結果是包含 char 類型之公用 Key 屬性的 IGrouping<TKey,TElement> 類型,以及包含群組中各個項目的 IEnumerable<T> 集合。The result of the query is an IGrouping<TKey,TElement> type that contains a public Key property of type char and an IEnumerable<T> collection that contains each item in the grouping.

group 子句的結果是一連串的序列。The result of a group clause is a sequence of sequences. 因此,若要存取每個所傳回群組內的個別項目,請在重複執行群組索引鍵的迴圈內使用巢狀 foreach 迴圈,如下列範例所示。Therefore, to access the individual elements within each returned group, use a nested foreach loop inside the loop that iterates the group keys, as shown in the following example.

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

範例Example

這個範例示範如何搭配使用「接續」與 into,以在建立其他邏輯之後,對群組執行這些邏輯。This example shows how to perform additional logic on the groups after you have created them, by using a continuation with into. 如需詳細資訊,請參閱 intoFor more information, see into. 下列範例會查詢每個群組,只選取其索引鍵值是母音的群組。The following example queries each group to select only those whose key value is a vowel.

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

備註Remarks

在編譯時期,group 子句會轉譯成 GroupBy 方法的呼叫。At compile time, group clauses are translated into calls to the GroupBy method.

另請參閱See also