LINQFarm: Understanding IEnumerable, Part I

The IEnumerable<T> interface is a key part of LINQ to Objects and binds many of its different features together into a whole. This series of posts explains IEnumerable<T> and the role it plays in LINQ to Objects. If you hear people talking about IEnumerable<T>, and sometimes wished you better understood its significance, then you should find this text helpful.

Collections and IEnumerable<T>

Though LINQ to Objects can be used to query several C# types, it cannot be used against all your in-process data sources. Those that can be queried all support the IEnumerable<T> interface. These include the generic collections found in the System.Collections.Generic namespace . The commonly used types found in this namespace include List<T> , Stack<T> , LinkedList<T> , Queue<T> , Dictionary<TKey, Value> and Hashset<T> .

All of the collections in the System.Collections.Generic namespace support the IEnumerable<T> interface. Here, for instance, is the declaration for List<T>:

 public class List<T> : IList<T>, ICollection<T>, IEnumerable<T>, IList, ICollection, IEnumerable

You will find IEnumerable<T> listed for all the other generic collections. It is no coincidence that these collections support IEnumerable<T>. Their implementation of this interface makes it possible to query them using LINQ to Objects.

LINQ to Objects and IEnumerable<T>

Consider the following simple LINQ query:

 List<int> list = new List<int> { 1, 3, 2 };
 // The LINQ Query expression
var query = from num in list
            where num < 3
            select num;
 foreach (var item in query)
{
    Console.WriteLine(item);
}

The type IEnumerable<T> plays two key roles in this code.

  • The query expression has a data source called list which implements IEnumerable<T>. 
  • The query expression returns an instance of IEnumberable<T>.

Every LINQ to Objects query expression, including the one shown above, will begin with a line of this type:

from x in y

In each case, the data source represented by the variable y must support the IEnumerable<T> interface. As you have already seen, the list of integers shown in this example supports that interface.

The same query shown here could also be written as follows:

 IEnumerable<int> query = from num in list
                         where num < 3
                         select num;

This code makes explicit the type of the variable returned by this query. As you can see, it is of type IEnumerable<int>. In practice, you will find that most LINQ to Objects queries return IEnumerable<T>, for some type T. The only exceptions are those that call a LINQ query operator that return a simple type, such as Count():

 int number = (from num in list
              where num < 3
              select num).Count();

In this case the query returns an integer specifying the number of items in the list created by this query. LINQ queries that return a simple type like this are an exception to the rule that LINQ to Objects queries operate on class that implement IEnumerable<T> and return an instance that supports IEnumerable<T>.

Composable

The fact that LINQ to Objects queries both take and return IEnumerable<T> enables a key feature of LINQ called composability. Because LINQ queries are composable you can usually pass the result of one LINQ query to another LINQ query. This allows you to compose a series of queries that work together to achieve a single end:

 List<int> list = new List<int> { 1, 3, 2 };

var query1 = from num in list
             where num < 3
             select num;

var query2 = from num in query1
             where num > 1
             select num;

var query3 = from num1 in query1
             from num2 in query2
             select num1 + num2;

Here the results of the first query are used as the data source for the second query, and the results of the first two queries are both used as data sources for the third query. If you print out the results of query3 with a foreach loop you get the numbers 3 and 4. Though it is not important to the current subject matter, you might have fun playing with the code to understand why these values are returned.

Summary

By now it should be clear to you that IEnumerable<T> plays a central role in LINQ to Objects. A typical LINQ to Objects query expression not only takes a class that implements IEnumerable<T> as its data source, but it also returns an instance of this same type. The fact that it takes and returns the same type enables a feature called composability.

The next logical question would be to ask why this type plays such a key role in LINQ to Objects. One simple answer would be that the creators of LINQ decided that it should be so, and hence it is so. But one can still ask why they picked this particular type. What is it about IEnumerable<T> that makes it a useful data source and return type for LINQ to Objects queries? The answer to that question will be found in the second part of this series of articles.

kick it on DotNetKicks.com