group 句 (C# リファレンス)group clause (C# Reference)

group 句は、グループのキー値に一致する 0 個以上の項目を含む 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;

この記事の「例」のセクションでは、into を含む場合と含まない場合の group の使用方法の完全な例があります。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

前のコード例では、char を使用していました。The 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;

ブールでグループ化するGrouping by bool

次の例では、結果を 2 つのグループに分割するためのキー用のブール値の用途を示します。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. group 句でメソッドを 2 度呼び出さなくて済むように、メソッド呼び出しの結果を格納する便利な場所として let を使用できます。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

1 つ以上のキーを使用して要素をグループ化するには、複合キーを使用します。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. キーの自動実装プロパティを使用して特殊クラスを作成し、Equals メソッドと GetHashCode メソッドをオーバーライドします。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 と共に continuation を使用し、グループに追加のロジックを実行する方法を示します。This example shows how to perform additional logic on the groups after you have created them, by using a continuation with into. 詳しくは、「into」をご覧ください。For 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