BlackWaspTM

This web site uses cookies. By using the site you accept the cookie policy.This message is for compliance with the UK ICO law.

LINQ
.NET 3.5+

LINQ Grouping

The sixth part of the LINQ to Objects tutorial examines grouping using the GroupBy standard query operator and equivalent query expression syntax. These allow a collection to be divided into smaller collections, each of which shares a common key.

Controlling Projection

As with other, non-grouping queries you can control the projection of the items that are placed into the groups. This is achieved using an element selector, which is a second delegate that returns the desired information. In the following code the first lambda expression is the key selector and groups items by category. The second lambda extracts the Name property from each item.

var groups = stock.GroupBy(s => s.Category, p => p.Name);

/* OUTPUT

Fruit
  Apple
  Banana
  Orange
Vegetable
  Cabbage
  Carrot
  Lettuce
Dairy
  Milk

*/

Using Result Selectors

For the final example of the GroupBy extension method we will add a result selector. A result selector is another delegate that allows the structure of the groups to be controlled. The result selector delegate accepts two arguments. The first is the key of a single group and the second is the list of items that are to be placed into the group. The delegate should return an object that represents the group, optionally including the items in the group.

It is easier to understand the use of a result selector with an example. Consider the code in the shaded box below. In this case three lambda expressions are being passed to the GroupBy method. The first is the key selector that specifies that the items will be grouped according to their Category property values. The second is the element selector that projects each item as a string containing the product name. The third is the result selector.

In the example, the result selector builds a new object of an anonymous type for each group. The two parameters of the lambda are category and items. For each group processed the category parameter will receive the key, which will be the category name as returned by the key selector. The items parameter will receive all of the items within the group, each being a string containing the Name property value, as defined in the element selector. The anonymous type value is constructed from the category (CategoryName), the number of items (ItemCount) and the items themselves (Items).

Try executing the code to see the results. Note that the foreach loops have been modified slightly because they are no longer working with IGrouping<T> objects. These have been replaced by the result selector's anonymous types.

var groups = stock.GroupBy(c => c.Category, s => s.Name,
    (category, items) => new
    {
        CategoryName = category,
        ItemCount = items.Count(),
        Items = items
    });

foreach (var group in groups)
{
    Console.WriteLine("{0}, {1} item(s)", group.CategoryName, group.ItemCount);

    foreach (var product in group.Items)
    {
        Console.WriteLine("  {0}", product);
    }
}

/* OUTPUT

Fruit, 3 item(s)
  Apple
  Banana
  Orange
Vegetable, 3 item(s)
  Cabbage
  Carrot
  Lettuce
Dairy, 1 item(s)
  Milk

*/

NB: There are several overloaded versions of the GroupBy clause to explore. They allow the use of comparers, key selectors, element selectors and results selectors in various combinations.

Grouping with Query Expression Syntax

In the remaining sections we will recreate some of the examples from above using query expression syntax. Not all of the samples can be converted to queries, as it is not possible to specify comparers.

Simple Grouping

The first example simply created a set of collections of stock items, grouped according to their category. We can recreate this with query expression syntax using the group clause. The clause has two parts. After the group keyword you specify the items that are to be grouped by providing the appropriate range variable. This is followed by the by keyword and an expression that defines the grouping. The sample can be recreated as follows:

var groups =
    from s in stock
    group s by s.Category;

Controlling Projection

The next example defines the projection of the items within the groups. To control the projection you can change the range variable after the group keyword to an expression. The following example returns only the Name property value for each item in the groups.

var groups =
    from s in stock
    group s.Name by s.Category;

into Clause

The into clause can be used with various operations, including selects, joins and groups. The clause causes the creation of a temporary variable for use within the query. The variable stores the results of a query and can itself be queried further. For this reason, it is sometimes called a continuation.

We can use a continuation to recreate the GroupBy syntax that uses a result selector. You can think of the query below as being in two sections. The first part groups the stock items by the category name and puts the generated IGrouping<T> objects into a temporary collection named, "category". The second part projects the items in the category variable into a collection of anonymous type objects containing the category name, item count and the items themselves.

var groups =
from s in stock
group s by s.Category into category
select new
{
    CategoryName = category.Key,
    ItemCount = category.Count(),
    Items = category
};
27 July 2010