C# IEnumerable

Summary: in this tutorial, you’ll learn about C# IEnumerable<T> interface which is the key to LINQ.

Introduction to the C# IEnumerable<T> interface

The IEnumerable<T> is an interface that represents a sequence of objects that can be enumerated.

The IEnumerable<T> has a single method GetEnumerator() that returns an IEnumerator<T> which can be used to iterate through a sequence of objects.

The IEnumerator<T> interface has the following important members:

  • Current – returns the current element in the sequence.
  • MoveNext() – moves the iterator to the next element in the sequence.

The following example defines a method Square() that accepts an integer argument called max and returns a sequence of square numbers of numbers from 0 to max:

using static System.Console;

IEnumerable<int> Square(int max)
{
    var results = new List<int>();
    for (int i = 0; i < max; i++)
    {
        results.Add(i * i);
    }
    return results;
}


foreach (var n in Square(5))
{
    WriteLine(n);
}Code language: C# (cs)

Output:

0
1
4
9
16Code language: C# (cs)

In this example, the Square() method adds the square numbers of all the numbers from 0 to max to a collection List<int>.

In case you want to construct a big list of square numbers, the implementation of the Square() method is not memory optimized because it creates a huge list of square numbers in the memory upfront.

It would be better if the Square() method returns a square number at a time based on the caller’s request.

The following Square() method uses the yield keyword to create a sequence of square numbers one at a time as requested by the caller:

using static System.Console;

IEnumerable<int> Square(int max)
{
    for (int i = 0; i < max; i++)
    {
        yield return i * i;
    }
}

var squares = Square(5);

foreach (var n in squares)
{
    WriteLine(n);
}Code language: C# (cs)

In this example, the Square() method will not be executed until its result (squares) is enumerated. One way to enumerate the sequence is to use it in a foreach loop.

So the following line doesn’t execute the Square() method immediately:

var squares = Square(5);Code language: C# (cs)

Instead, it’ll wait until the squares sequence is enumerated. This is called a lazy evaluation.

When the foreach loop statement enumerates the element of the squares sequence, the Square() method is executed that yields a square number one at a time in each iteration:

foreach (var n in squares)
{
    WriteLine(n);
}Code language: C# (cs)

IEnumerable<T> and LINQ

LINQ queries rely heavily on lazy evaluation. In LINQ, lazy evaluation is often referred to as deferred query execution or deferred query evaluation.

Deferred query evaluation is important because it allows you to define a query at one point and use it later, exactly when you want to, as many times as needed. For example:

using static System.Console;

var numbers = new List<int> { 1, 5, 2, 8, 9 };

var oddNumbers = numbers
                    .Where(number => number % 2 != 0)
                    .Select(number => number);

foreach (var number in oddNumbers)
{
    WriteLine(number);
}Code language: C# (cs)

In this example, the oddNumbers variable has the type of IEnumerable<int>. Due to the deferred query evaluation, the query does not execute until the foreach statement.

Summary

  • IEnumerable<T> interface uses lazy evaluation i.e., the sequence is not generated until it is evaluated.
  • Because of the IEnumerable<T>, LINQ can work with large sequences of objects without requiring excessive memory.
Was this tutorial helpful ?