In this article, we are going to learn about IEnumerable in C#. IEnumerable acts as an abstraction over a collection and allows us to iterate over it without knowing the actual type of the collection.

To download the source code for this article, you can visit our GitHub repository.

Let’s begin.

What is the IEnumerable Interface in C#?

The IEnumerable interface is the base for all the non-generic collections that can be enumerated.

It exposes a single method GetEnumerator(). This method returns a reference to yet another interface System.Collections.IEnumerator.

The IEnumerator interface in turn provides the ability to iterate through a collection by using the methods MoveNext() and Reset(), and the property Current.

If you’d like to learn about the different collection types available in .NET, check out our .NET Collections series.

Hence, in order for us to be able to use ForEach over a collection, it must implement IEnumerable.

Extension Methods in IEnumerable

The IEnumerable interface includes some extension methods along with the GetEnumerator() method. 

We use deferred execution to implement these methods, so the actual value of the return object is realized only when the object is enumerated either using a ForEach loop or by calling its GetEnumerator() method.

Cast<TResult>

This method takes a sequence of type IEnumerable and converts all the objects of the given sequence to the type TResult, thus resulting in a return type IEnumerable<TResult>.

The Cast<TResult>() method is a generic method where TResult acts as a placeholder for the actual type we want to work with at compile time. It allows the use of standard LINQ query operators like filtering, sorting, etc. on a non-generic type such as Arraylist:

var primes = new ArrayList
{
    5,
    3,
    2,
    7
};

var primeQuery = primes.Cast<int>().OrderBy(prime => prime).Select(prime => prime);

var expected = new List<int> { 2, 3, 5, 7 };

Assert.Equal(expected, primeQuery);

We are able to convert a non-generic collection of type Arraylist into IEnumerable<int>and thus have all its functionalities available.

OfType<TResult>

This method takes a sequence of type IEnumerable and filters it based on the specified type. It returns only those elements from the input source that can be cast to the type TResult:

var cities = new ArrayList
{
    "London",
    "Paris",
    "Madrid",
    "Berlin",
    7,
    "Lisbon"
};

var cityQuery = cities.OfType<string>();

var expected = new List<string> { "London", "Paris", "Madrid", "Berlin", "Lisbon" };

Assert.Equal(expected, cityQuery);

This is where it differs from Cast<TResult>() which instead throws an exception if an element can’t be cast to TResult. We can also use standard query operators such as Where() after filtering the source:

var cities = new ArrayList
{
    "London",
    "Paris",
    "Madrid",
    "Berlin",
    7,
    "Lisbon",
    8,
    12
};

var evens = cities.OfType<int>().Where(x => x % 2 == 0);

var expected = new List<int> { 8, 12 };

Assert.Equal(expected, evens);

AsParallel Method

This method enables the parallelization of a query. It takes a sequence of type IEnumerable and converts it to the type ParallelQuery:

var numbers = Enumerable.Range(0, 100);

var oddsCount = numbers.AsParallel().Count(x => x % 2 != 0);

var expected = 50;

Assert.Equal(expected, oddsCount);

The AsParallel()method binds the query to PLINQ or Parallel LINQ. The PLINQ queries work in a similar way to the sequential LINQ queries where they operate on in-memory data sources and allow deferred execution.

Wanna join Code Maze Team, help us produce more awesome .NET/C# content and get paid? >> JOIN US! <<

However, they attempt to make use of all available processors by partitioning the data source into segments and then executing the query on each segment on separate worker threads in parallel on multiple processors. 

AsQueryable Method

This method takes a sequence of type IEnumerable and converts it into IQueryable:

var numbers = new List<int>
{
    1, 2, 3, 4, 5, 6, 7, 8, 9, 10
};

var query = numbers.AsQueryable();

var multiplesOf3 = query.Where(x => x % 3 == 0);

var expected = new List<int> { 3, 6, 9 };

Assert.Equal(expected, multiplesOf3);

Assert.Equal("Constant", query.Expression.NodeType.ToString());

The IQueryable interface extends the IEnumerable interface and hence is similar in functionality.

The main difference between both is that while IEnumerable is more suited to collections loaded using LINQ where filtering is required on the client side where IEnumerable is, IQueryable creates a SQL Query on the server side and only filtered data is sent to the client side.

This concludes the extension methods of IEnumerable in C#. Let’s now take a look at implementing a custom IEnumerable interface in code.

Code Implementation of IEnumerable Interface

Let’s start by creating a Bookclass:

public class Book
{
    public Book(string name, string author)
    {
        Name = name;
        Author = author;
    }

    public string Name { get; set; }
    public string Author { get; set; }
}

Now, that we have our business object, let’s create a collection of this object:

public class Library : IEnumerable
{
    private readonly Book[] _books;

    public Library(Book[] books)
    {
        _books = new Book[books.Length];

        for (int i = 0; i < books.Length; i++)
        {
            _books[i] = books[i];
        }
    }
}

The class Library implements IEnumerable so that we may use the ForEach loop on the class objects. However, the code does not compile until we implement the GetEnumerator() method of the interface.

Wanna join Code Maze Team, help us produce more awesome .NET/C# content and get paid? >> JOIN US! <<

As we need to implement IEnumerator when implementing IEnumerable, let’s first implement the IEnumerator interface:

public class BookEnumerator : IEnumerator
{
    private readonly Book[] _books;
    private int _index;

    public BookEnumerator(Book[] books)
    {
        _books = books;
    }

    public Book Current
    {
        get
        {
            return _books[_index];
        }
    }

    object IEnumerator.Current => Current;

    public bool MoveNext()
    {
        _index++;
        return _index < _books.Length;
    }

    public void Reset()
    {
        _index = -1;
    }
}

We start by implementing the required members of IEnumerator in the class BookEnumerator i.e. the property Current, and the methods Reset() and MoveNext().

When the class is instantiated, the enumerator _index is placed before the first element of the collection. When iterating over the collection, we send a call to MoveNext() that advances the enumerator to the first position. 

The Current property indicates the position of the enumerator at any point. It returns the same value until MoveNext() or Reset() are called. It is undefined under these conditions:

  • When the enumerator places before the first element of the collection, immediately after the instantiation
  • When the last call to MoveNext() returns false i.e. the iteration of the collection is complete
  • If any change in the collection such as addition, modification, etc. invalidates the enumerator

The Reset() method resets the enumerator position to the original. The MoveNext() method advances _index till it reaches the position after the last element of the collection. After this, the MoveNext() method always returns false.

With this step done, we are now ready to revisit our Library class and get rid of the compiler errors due to not implementing the GetEnumerator() method:

public class Library : IEnumerable
{
    //Existing Code excluded for brevity

    public BookEnumerator GetEnumerator() => new BookEnumerator(_books);
        
    IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
        
}

Now let’s see the Library class in action as an implementation of IEnumerable:

var books = new Book[5]
{
    new Book("Anna Karenina", "Leo Tolstoy"),
    new Book("To Kill a Mockingbird", "Harper Lee"),
    new Book("The Great Gatsby", "F. Scott Fitzgerald"),
    new Book("One Hundred Years of Solitude", "Gabriel García Márquez"),
    new Book("A Passage to India", "E.M. Forster")
};

var library = new Library(books);

foreach (var book in library)
{
    Console.WriteLine($"Book: {book.Name}, Author: {book.Author}");
}

The IEnumerable interface allows the use of ForEach loop on the collection of type Library. On running the program we receive the expected output:

Wanna join Code Maze Team, help us produce more awesome .NET/C# content and get paid? >> JOIN US! <<
Book: Anna Karenina, Author: Leo Tolstoy
Book: To Kill a Mockingbird, Author: Harper Lee
Book: The Great Gatsby, Author: F. Scott Fitzgerald
Book: One Hundred Years of Solitude, Author: Gabriel García Márquez
Book: A Passage to India, Author: E.M. Forster

Conclusion

In this article, we learned about IEnumerable in C#. We learned how it is necessary to implement for us to iterate over a collection. We learned the associated extension methods and their implementations.