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.

Grouping Data

LINQ provides the ability to organise information into groups. Using either the standard query operators or query expression syntax, you can specify a key based upon the data held in a collection. The source data is then segregated into several enumerable lists, each containing all of the items with a matching key. For example, you may group a collection of stock items by their categories. The result is a group of collections, one for each unique category, each containing all of the products in that category.

Grouping of data has many uses. You may decide to group a large data set and display one group at a time through the user interface. The user may be able to change the visible group using a combo box or selection of radio buttons. You may also group the information so that you can aggregate the data, obtaining sums, averages or other aggregations for each group.

GroupBy Standard Query Operator

We will begin by examining the GroupBy standard query operator. This is an extension method of the IEnumerable<T> interface that performs grouping. Before we can begin, we need a class to work with. This will be the same StockItem type that we used in the LINQ Results Ordering article:

public class StockItem
{
    public string Name { get; set; }
    public string Category { get; set; }
    public double Price { get; set; }

    public StockItem(string name, string category, double price)
    {
        Name = name;
        Category = category;
        Price = price;
    }

    public override string ToString()
    {
        return string.Format("{0}/{1}/{2}", Name, Category, Price);
    }
}

For each example we will use the same stock item source data. To initialise the collection use the following code:

var stock = new List<StockItem>
{
    new StockItem("Apple", "Fruit", 0.30),
    new StockItem("Banana", "Fruit", 0.35),
    new StockItem("Orange", "Fruit", 0.29),
    new StockItem("Cabbage", "Vegetable", 0.49),
    new StockItem("Carrot", "Vegetable", 0.29),
    new StockItem("Lettuce", "Vegetable", 0.30),
    new StockItem("Milk", "Dairy", 1.12)
};

Simple Grouping

The simplest version of the GroupBy method accepts a single parameter containing a Func delegate, usually a lambda expression. This key selector expression is used to extract a key value from each item in the source collection. All items with a matching key are placed into one of the group collections that are returned when the query is executed.

The results of the grouping query are returned as an IEnumerable collection of IGrouping objects. An IGrouping object is simply a collection of items that have the same key. IGrouping<T> implements IEnumerable<T> and adds a new Key property that contains the shared key value.

In the sample code below, the key selector function causes grouping according to the Category property. In many cases, grouping is performed upon a single property or the results of a simple expression. However, you can create grouping based upon multiple properties by using a key selector that returns a combination of properties in an anonymous type. For example, you could group by Category and Name using the selector, s => new { s.Category, s.Name }.

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

As the result of the query is a collection of collections, we can use two nested foreach loops to output the data. Below is an outer loop that displays the Key property for each group, and an inner loop that shows an indented list of the individual items.

foreach (var group in groups)
{
    Console.WriteLine(group.Key);

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

You can now run the program to execute the query and show the results. The output should be as follows:

Fruit
  Apple/Fruit/0.3
  Banana/Fruit/0.35
  Orange/Fruit/0.29
Vegetable
  Cabbage/Vegetable/0.49
  Carrot/Vegetable/0.29
  Lettuce/Vegetable/0.3
Dairy
  Milk/Dairy/1.12

Using Comparers

Unless otherwise specified, grouping uses the default comparer for the results of the key selector delegate. If there are slight differences between two keys, the items will be placed into separate groups. To see this, modify the sample data so that the "Banana" item has the category, "fruit", instead of "Fruit". If you run the program again you will see that the banana object is placed in its own group.

You can change the comparer that is used to determine the grouping. The comparer can be a standard class from the .NET framework or a custom comparer. The only restriction is that it must implement the EqualityComparer<T> interface.

To demonstrate, try changing the query to the following code. This uses a case-insensitive string comparer so that stock items with categories that differ only in case are combined into the same group. After testing the code you should return the banana's category to "Fruit".

var groups = stock.GroupBy(s => s.Category, StringComparer.OrdinalIgnoreCase);
27 July 2010