In C#, the term “enumerable” refers to any object that implements the IEnumerable
or IEnumerable<T>
interface. These interfaces provide a mechanism to iterate over a collection of elements.
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();
}
There is also the generic version IEnumerable<T>
, which inherits from IEnumerable
.
public interface IEnumerable<out T> : IEnumerable
{
IEnumerator<T> GetEnumerator();
}
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; }
}
That is, it is basically an element that,
- Allows iterating over a series of elements
- Contains a reference to the current element
Current
and the nextMoveNext()
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);
}
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();
}
}
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;
}
}
In this example, the method GetEvenNumbers 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 manner. 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);
}
Avoid Modifying Collections During Iteration
Modifying a collection while iterating over it can cause exceptions or unexpected behavior. It will 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);
}
Understanding Deferred Execution
LINQ queries and methods that use yield return
are evaluated in a deferred manner. That is, they are not executed until you iterate over the collection.
This is a very interesting feature, but it can also be a source of errors if we do not understand it correctly.
List<int> numbers = new List<int> { 1, 2, 3, 4, 5 };
var filtered = numbers.Where(n => n > 3);
Console.WriteLine(filtered.Sum()); // 9
numbers.Add(6);
Console.WriteLine(filtered.Sum()); // 15
In the example,
- We create a collection of numbers from 1 to 5,
- We use
Where
to filter numbers greater than 3 - We display the sum, which is 9 (4 + 5)
- We add a 6
- We display the sum, which is now 16
This happens because Where
is a method that works with enumerables. Therefore, filtered
is not a collection of numbers, but an iterable that depends on numbers
.
By adding the number 6 to numbers
, filtered
is modified, and thus its sum.