CodeGym /Courses /C# SELF /Enumerability Contract: IE...

Enumerability Contract: IEnumerable<T>

C# SELF
Level 28, Lesson 1
Available

1. Introduction

As you might've already noticed, different C# collections have some family traits. For example, you can loop through almost any of them using a foreach loop:


var names = new List<string> { "Anya", "Boris", "Vika" };

// Heads up — this code works with List, arrays, and even HashSet!
foreach (var name in names)
{
    Console.WriteLine(name);
}

What's the magic? It's all because all modern collections implement the IEnumerable<T> interface — kind of a "contract" that guarantees the collection can give you its elements one by one, in order.

Imagine a company where for any collection of employees (could be a department, a project team, or a party guest list) there's a rule: if an object implements "IEnumerable", you can always go through all the employees in a certain order, no matter "how they're stored", the main thing is — you can enumerate them.

2. The IEnumerable<T> Interface — What's Inside?

Let's see what this interface looks like in .NET:


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

It has just one method — GetEnumerator, which returns an object of type IEnumerator<T>. This object is in charge of the actual "enumeration" process: it knows which element is current, how to move to the next one, and when it's all over.

In short: if a class (or collection) implements IEnumerable<T>, you can loop through it with foreach, get elements one by one — and it doesn't matter how they're implemented inside: could be a list, a hash table, or even on a hard drive.

Mini-Diagram


Collection (for example, List<T>)
    |
    v
IEnumerable<T>
    |
    v
IEnumerator<T> (the real "element walker")

3. Practice and Code Universality

A huge plus of this contract is universality. If a function or method takes IEnumerable<T> as input, it means it's compatible with any kind of collection — from arrays and lists to hash sets, queues, and even your own custom collections!

For example, check out this function that sums up elements:


// You can sum up any set of int that supports IEnumerable<int>
int Sum(IEnumerable<int> numbers)
{
    int result = 0;
    foreach (var n in numbers)
        result += n;
    return result;
}

// Works with List<int>
var list = new List<int> { 1, 2, 3 };
Console.WriteLine(Sum(list));

// Works with arrays!
int[] array = { 4, 5, 6 };
Console.WriteLine(Sum(array));

// Even works with the result of a method that filters elements
Console.WriteLine(Sum(list.Where(x => x % 2 == 0))); // Using LINQ

Real-Life Example: Universal Methods

Imagine you need to write a utility to find the longest word in any collection of strings. Thanks to IEnumerable<string>, you can do this for any source — array, list, result of filter methods, etc.:


string FindLongest(IEnumerable<string> words)
{
    string longest = "";
    foreach (var word in words)
        if (word.Length > longest.Length)
            longest = word;
    return longest;
}

You can use it with any suitable set.

4. Why Does foreach Work with IEnumerable<T>?

A common question: why does the foreach loop "understand" any collection? The answer is simple: the C# compiler looks for a GetEnumerator method in the class and expects to get an object with MoveNext() and a Current property. That's the standard interface — IEnumerator<T>.

Thanks to this approach, you can even write your own class that "gives out" elements one by one (like a Fibonacci sequence generator), and if it implements IEnumerable<int>, you can use it in foreach just like a regular list.

5. What is an Enumerator and How Does It Work?

Inside any collection that implements IEnumerable<T>, there's a special "walker" — the Enumerator (or, to be nerdy: an object that implements IEnumerator<T>). It's the one that "drags" elements from the collection one by one when you write a foreach loop.

The IEnumerator<T> Interface

Here's what a standard Enumerator can do:


public interface IEnumerator<T> : IDisposable
{
    T Current { get; }         // Current element
    bool MoveNext();           // Move to the next element
    void Reset();              // Go back to the start (rarely used)
}

How does it work under the hood?

  • MoveNext() — moves the "pointer" to the next element and returns true if there's an element. If there are no more elements — returns false.
  • Current — gives you the current element (the one the enumerator is pointing at right now).
  • Reset() — resets the Enumerator to the start (but almost never used).
  • Dispose() — frees up resources (needed for collections that might work with files or the network).

Example: How foreach Works "Under the Hood"

When you write:


var numbers = new List<int> { 1, 2, 3 };
foreach (var n in numbers)
    Console.WriteLine(n);

Actually, the compiler turns it into something like this:


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

// Get the "walker"
var enumerator = numbers.GetEnumerator();
while (enumerator.MoveNext())
{
    var n = enumerator.Current;
    Console.WriteLine(n);
}
// The compiler automatically calls Dispose() in a using block (if enumerator implements IDisposable)

Important: if a collection works with external resources (like files or databases), the Enumerator can free them up automatically when the walk is done.

Visual Diagram


Start of walk -> GetEnumerator() -> Enumerator
         |
         v
  MoveNext() -> Current
         |
         v
  MoveNext() -> Current
         |
        ...
         |
         v
  MoveNext() == false -> end of walk

6. IEnumerable and Arrays, Lists, Sets: Who's Related to Whom

Let's see which standard .NET containers implement this interface:

Collection Type Implements IEnumerable<T>? Can you use foreach?
Array (
int[]
)
✅ ✅
List<T>
✅ ✅
Dictionary<TKey, V>
✅ (by pairs, keys, values) ✅
HashSet<T>
✅ ✅
Queue<T>
✅ ✅
Stack<T>
✅ ✅

Even a string (string) implements regular IEnumerable, so you can loop through each character in a string the same way.

7. Making Your Own Enumerable

Fun challenge: let's try to make a minimal collection that stores even numbers from 0 to N and implements IEnumerable<int>. Then we can loop through it and use it with LINQ.


// A collection class you can "walk through"
class EvenNumbers : IEnumerable<int>
{
    private int max;

    public EvenNumbers(int max)
    {
        this.max = max;
    }

    public IEnumerator<int> GetEnumerator()
    {
        for (int i = 0; i <= max; i += 2)
            yield return i; // Special magic for implementing the enumerator
    }

    // Explicit implementation of non-generic IEnumerable for backward compatibility
    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }
}

// Usage:
var evens = new EvenNumbers(10);
foreach(var e in evens)
    Console.Write($"{e} "); // 0 2 4 6 8 10

Notice the key detail: if your class implements IEnumerable<T> — you automatically make it compatible with most .NET tools: LINQ, foreach, methods that take an enumerable.

8. Typical Mistakes and Gotchas

Sometimes newbie devs think IEnumerable<T> is some kind of separate collection that has elements. Actually — it's just a "promise" that if we start enumerating, we'll be "given" elements one by one.

If you need random access by index (myList[5]), or want to use methods like Add or Remove — the IEnumerable<T> interface won't help. It's only for sequential walking!

Mistake: trying to modify a collection while looping through it. For example:


foreach (var item in myList)
{
    if (item < 0)
        myList.Remove(item); // DANGEROUS! InvalidOperationException
}

It's better to first make a separate list for removal, or use methods that create a new collection:


// Safe way — create a new collection manually
var newList = new List<int>();
foreach (var item in myList)
{
    if (item >= 0)
        newList.Add(item);
}
myList = newList;

// Or remove by indexes in reverse order
for (int i = myList.Count - 1; i >= 0; i--)
{
    if (myList[i] < 0)
        myList.RemoveAt(i);
}
2
Task
C# SELF, level 28, lesson 1
Locked
Implementing a custom collection that supports IEnumerable
Implementing a custom collection that supports IEnumerable
Comments
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION