csharp-enumerables

What are Enumerables in C#

  • 4 min

In C#, enumerables are a mechanism for iterating over collections of elements. For example, they can be used in FOR EACH loops or combined with each other using LINQ.

Technically, internally an enumerable refers to any object that implements the IEnumerable or IEnumerable<T> interface.

Let’s see it in detail 👇.

IEnumerable and IEnumerator Interfaces

The IEnumerable interface defines a single method GetEnumerator(), which returns an IEnumerator. The enumerator provides the necessary functionality to iterate through a collection.

public interface IEnumerable
{
    IEnumerator GetEnumerator();
}
Copied!

There is also the generic version IEnumerable<T>, which inherits from IEnumerable.

public interface IEnumerable<out T> : IEnumerable
{
    IEnumerator<T> GetEnumerator();
}
Copied!

The IEnumerable interface is defined in the System.Collections namespace and its generic version IEnumerable<T> in System.Collections.Generic.

IEnumerator Interface

On the other hand, the IEnumerator interface and its generic equivalent IEnumerator<T> look like this.

public interface IEnumerator
{
    bool MoveNext();
    object Current { get; }
    void Reset();
}

public interface IEnumerator<out T> : IEnumerator
{
	T Current { get; }
}
Copied!

That is, basically it is an element that,

  • Allows iterating over a series of elements
  • Contains a reference to the current element Current and the next one MoveNext()

How to Use Enumerables

The most common way to iterate over an enumerable is by using foreach. This loop simplifies the iteration syntax by hiding the details of the enumerator.

List<int> numbers = new List<int> { 1, 2, 3, 4, 5 };

foreach (int number in numbers)
{
    Console.WriteLine(number);
}
Copied!

Implementing a Custom Enumerable

We can create a class that implements IEnumerable<T> to define a custom collection.

Here is an example of an Enumerable that generates a sequence of even numbers.

public class Evens : IEnumerable<int>
{
    private int _max;

    public Evens(int max)
    {
        _max = max;
    }

    public IEnumerator<int> GetEnumerator()
    {
        for (int i = 0; i <= _max; i += 2)
        {
            yield return i;
        }
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }
}
Copied!

Using yield return

The yield return keyword simplifies the creation of custom enumerators. We can use it to return elements on the fly.

public static IEnumerable<int> GenerateEvens(int max)
{
    for (int i = 0; i <= max; i += 2)
    {
        yield return i;
    }
}
Copied!

In this example, the GenerateEvenNumbers method uses yield return to return even numbers up to a specified maximum.

Using LINQ with IEnumerable

LINQ (Language Integrated Query) allows us to perform queries on collections in a declarative way. LINQ queries work with any collection that implements IEnumerable<T>.

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

var evenNumbers = from number in numbers
                  where number % 2 == 0
                  select number;

foreach (int number in evenNumbers)
{
    Console.WriteLine(number);
}
Copied!

Avoid Modifying Collections During Iteration

Modifying a collection while iterating over it can cause exceptions or unexpected behavior. It will most likely end in 💥.

Instead, we should create a new collection with the modified elements.

List<int> numbers = new List<int> { 1, 2, 3, 4, 5 };
List<int> duplicatedNumbers = new List<int>();

foreach (int number in numbers)
{
    duplicatedNumbers.Add(number * 2);
}
Copied!