Ienumerable And Ienumerator In C# Complete Guide

 Last Update:2025-06-23T00:00:00     .NET School AI Teacher - SELECT ANY TEXT TO EXPLANATION.    8 mins read      Difficulty-Level: beginner

Understanding the Core Concepts of IEnumerable and IEnumerator in C#

IEnumerable and IEnumerator in C#

C# provides interfaces within the System.Collections and System.Collections.Generic namespaces to standardize collection access. Among them, IEnumerable and IEnumerator are essential for iteration over collections. Understanding their roles is crucial for developing clean, efficient code that manipulates and accesses data stored in collections.

IEnumerable Interface

The IEnumerable<T> interface is part of the .NET framework that represents a simple collection of elements of type T. This interface provides one method:

  • GetEnumerator(): Returns an enumerator that iterates through the collection.

By implementing IEnumerable<T>, a class gains the ability to participate in foreach loops, making it easier to iterate over elements.

Important Uses:

  1. Collection Iteration: Any class that implements IEnumerable<T> can be iterated over using a foreach loop or LINQ expressions.
  2. Custom Collections: Developers often define custom collections that implement IEnumerable<T> so that they can use common iteration methods.
  3. Deferred Execution: LINQ queries use IEnumerable<T> for deferred execution, meaning that the query is only evaluated when the collection is iterated over, which can result in performance improvements.

Example Implementation: Consider a simple collection class that holds integers and implements IEnumerable<T>.

using System;
using System.Collections;
using System.Collections.Generic;

public class IntegerCollection : IEnumerable<int>
{
    private int[] _numbers = new int[5];

    public IntegerCollection()
    {
        for (int i = 0; i < 5; i++)
        {
            _numbers[i] = i * 2; // Initialize with even numbers.
        }
    }

    public IEnumerator<int> GetEnumerator()
    {
        foreach (var number in _numbers)
        {
            yield return number;
        }
    }

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

To use this custom collection in a foreach loop:

IntegerCollection collection = new IntegerCollection();

foreach (int num in collection)
{
    Console.WriteLine(num);
}

// Output:
// 0
// 2
// 4
// 6
// 8

IEnumerator Interface

The IEnumerator<T> interface encapsulates the logic for iterating over a collection, keeping track of the current element in the sequence and providing access to that element. It has three fundamental members:

  • Current Property: Gets the element in the collection at the current position of the enumerator.
  • MoveNext Method: Advances the enumerator to the next element of the collection. Returns true if the enumerator was successfully advanced to the next element; false if the enumerator has passed the end of the collection.
  • Reset Method: Sets the enumerator to its initial position, which is typically before the first element. Note: this might not be supported by all implementations.

Important Concepts:

  1. Manual Iteration: While foreach abstracts away many of the details, manually handling an IEnumerator gives you more control over the iteration process.
  2. Resource Management: Properly managing resources and ensuring no memory leaks occur is vital. The using statement can help by disposing of IEnumerator objects that implement IDisposable.

Example Usage:

using System;

public class IntegerIterator : IEnumerator<int>
{
    private int[] _numbers = new int[] { 1, 2, 3, 4, 5 };
    private int _index = -1;

    public void Dispose()
    {
        // In this case, dispose may not be needed as we don't have additional resources.
    }

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

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

    public int Current
    {
        get
        {
            if (_index < 0 || _index >= _numbers.Length)
            {
                throw new InvalidOperationException("The enumerator is positioned before the first element or after the last element.");
            }
            return _numbers[_index];
        }
    }

    object IEnumerator.Current => Current;
}

class Program
{
    static void Main(string[] args)
    {
        var iterator = new IntegerIterator();

        while (iterator.MoveNext())
        {
            Console.WriteLine(iterator.Current);
        }
        
        iterator.Dispose();
    }
}

The IntegerIterator class here manually implements IEnumerator<int>, allowing direct management of the iteration process.

Key Differences

  • Role: IEnumerable<T> abstracts away the specifics of how a collection is iterated and provides a way to get an IEnumerator<T>. IEnumerator<T> is responsible for maintaining the state of iteration through a collection.
  • Use Case: Use IEnumerable<T> to allow collection types to be used in foreach or LINQ queries. Use IEnumerator<T> for more precise and fine-grained control over the iteration process.

Performance Considerations

Implementing IEnumerable<T> is generally lightweight because it defers the work of creating the enumerator until it is required, and the enumerator defers actually producing the data until it is consumed. Using yield return can greatly simplify the implementation and improve readability without significant performance overhead.

Common Pitfalls

  • Enumerator State: An IEnumerator<T> maintains the state of iteration. Repeatedly calling GetEnumerator() on the same collection does not synchronize state across enumerators, which means that each enumerator starts the iteration from the beginning.
  • Resource Disposal: Always ensure that objects which implement IDisposable like IEnumerator<T> are properly disposed. Failing to do so leads to resource leakage.

Understanding IEnumerable<T> and IEnumerator<T> allows developers to create more flexible and powerful classes capable of interacting with a wide range of data-handling scenarios in C#. This knowledge is foundational to working effectively with collections and performing data operations efficiently.

Online Code run

🔔 Note: Select your programming language to check or run code at

💻 Run Code Compiler

Step-by-Step Guide: How to Implement IEnumerable and IEnumerator in C#

Complete Examples, Step by Step for Beginners: IEnumerable and IEnumerator in C#

Step 1: What are IEnumerable and IEnumerator?

  • IEnumerable: This is an interface that exposes an enumerator, which supports a simple iteration over a non-generic collection or a generic collection. It defines one method: GetEnumerator().

  • IEnumerator: This is an interface that enables a simple iteration over a non-generic collection or a generic collection. It includes the following properties and methods:

    • Current: Gets the current element in the collection.
    • MoveNext(): Advances the enumerator to the next element of the collection.
    • Reset(): Sets the enumerator to its initial position, which is before the first element in the collection.

In C#, most collection types (like List<T>, Array, Dictionary<TKey, TValue>) implement IEnumerable (or the generic IEnumerable<T>).

Step 2: Implementing IEnumerable for a Custom Collection

Let's create a simple custom collection that implements IEnumerable to understand how it works.

using System;
using System.Collections;

public class MyCollection : IEnumerable
{
    private string[] _items = { "Apple", "Banana", "Cherry" };

    public IEnumerator GetEnumerator()
    {
        for (int i = 0; i < _items.Length; i++)
        {
            yield return _items[i];
        }
    }
}

class Program
{
    static void Main()
    {
        MyCollection myCollection = new MyCollection();

        foreach (var item in myCollection)
        {
            Console.WriteLine(item);
        }
    }
}

Explanation:

  1. We create a class MyCollection that implements the IEnumerable interface.
  2. The GetEnumerator() method is overridden to return an IEnumerator. Inside this method, we use yield return to iterate over items in the collection. yield return simplifies the syntax for creating custom iterators.
  3. In the Main method, we create an instance of MyCollection and use a foreach loop to iterate through the items.

Step 3: Implementing IEnumerator for a Custom Collection

Now, let's manually implement IEnumerator without using yield return to understand how it works behind the scenes.

using System;
using System.Collections;

public class MyCollection : IEnumerable
{
    private string[] _items = { "Apple", "Banana", "Cherry" };

    public IEnumerator GetEnumerator()
    {
        return new MyCollectionEnumerator(_items);
    }
}

public class MyCollectionEnumerator : IEnumerator
{
    private string[] _items;
    private int _index = -1;

    public MyCollectionEnumerator(string[] items)
    {
        _items = items;
    }

    public object Current
    {
        get
        {
            if (_index == -1)
                throw new InvalidOperationException("Enumeration has not started.");
            if (_index >= _items.Length)
                throw new InvalidOperationException("Enumeration has finished.");

            return _items[_index];
        }
    }

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

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

class Program
{
    static void Main()
    {
        MyCollection myCollection = new MyCollection();

        foreach (var item in myCollection)
        {
            Console.WriteLine(item);
        }
    }
}

Explanation:

  1. We create a custom enumerator class MyCollectionEnumerator that implements IEnumerator.
  2. Inside the MyCollectionEnumerator class:
    • The constructor receives an array of items and initializes the _index property.
    • The Current property returns the current item based on the _index. If _index is out of bounds, it throws an InvalidOperationException.
    • The MoveNext() method advances the _index and returns true if there are more items to iterate over.
    • The Reset() method sets the _index back to -1.
  3. In MyCollection.GetEnumerator(), we return an instance of MyCollectionEnumerator initialized with the items.
  4. The foreach loop in the Main method works as usual, but now it uses our custom enumerator.

Step 4: Using Generic Versions (IEnumerable and IEnumerator)

Using the generic versions of IEnumerable<T> and IEnumerator<T> is preferred because they provide type safety and eliminate the need for casting.

Here's how you can modify the previous example to use generics.

using System;
using System.Collections;
using System.Collections.Generic;

public class MyCollection : IEnumerable<string>
{
    private string[] _items = { "Apple", "Banana", "Cherry" };

    public IEnumerator<string> GetEnumerator()
    {
        return new MyCollectionEnumerator(_items);
    }

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

public class MyCollectionEnumerator : IEnumerator<string>
{
    private string[] _items;
    private int _index = -1;

    public MyCollectionEnumerator(string[] items)
    {
        _items = items;
    }

    public string Current
    {
        get
        {
            if (_index == -1)
                throw new InvalidOperationException("Enumeration has not started.");
            if (_index >= _items.Length)
                throw new InvalidOperationException("Enumeration has finished.");

            return _items[_index];
        }
    }

    object IEnumerator.Current => Current;

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

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

    public void Dispose()
    {
        // Typically, you would release resources here.
        // In this simple example, it is not necessary.
    }
}

class Program
{
    static void Main()
    {
        MyCollection myCollection = new MyCollection();

        foreach (var item in myCollection)
        {
            Console.WriteLine(item);
        }
    }
}

Explanation:

  1. We change MyCollection to implement IEnumerable<string> instead of IEnumerable.
  2. GetEnumerator() now returns an IEnumerator<string>.
  3. We add an implicit implementation of the non-generic GetEnumerator() to satisfy the IEnumerable requirement.
  4. MyCollectionEnumerator implements IEnumerator<string> instead of IEnumerator.
  5. The Current property is now strongly typed to string.
  6. We add an explicit implementation of the non-generic Current property to satisfy the IEnumerator requirement.
  7. The Dispose() method is also implemented but does not perform any action in this simple example.

Top 10 Interview Questions & Answers on IEnumerable and IEnumerator in C#

1. What is the difference between IEnumerable and IEnumerator?

IEnumerable: It is an interface representing a collection of objects that can be iterated over using a single direction, forward-only enumerator. It only contains one method: GetEnumerator(). It's used when you want to expose a sequence of elements to the consumer without revealing its details (like the exact data structure used).

IEnumerator: This interface also represents an enumerator but allows more functionality. It provides methods to move to the next item (MoveNext()), retrieve the current item (Current property), and reset the enumerator back to its initial position (Reset() method). However, the Reset() method is deprecated and should not be used. Typically, IEnumerator is returned by the GetEnumerator() method of IEnumerable.

2. How do you implement IEnumerable in your class?

To implement IEnumerable, your class must provide the GetEnumerator() method:

using System;
using System.Collections;
using System.Collections.Generic;

public class MyCollection : IEnumerable<int>
{
    private int[] _items = { 1, 2, 3, 4 };

    public IEnumerator<int> GetEnumerator()
    {
        foreach (var item in _items)
        {
            yield return item;
        }
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return this.GetEnumerator();
    }
}

Here, yield return is syntactic sugar that simplifies creating enumerators without implementing IEnumerator directly.

3. When would you use IEnumerator?

You would use IEnumerator when you need direct control over iteration, such as resetting or accessing the current item multiple times within a loop. However, it's rare to implement IEnumerator explicitly since yield return in the context of IEnumerable can handle most scenarios:

using System;
using System.Collections;
using System.Collections.Generic;

public class MyIEnumerator<T> : IEnumerator<T>
{
    private T[] _items;
    private int _position = -1;

    public MyIEnumerator(T[] items)
    {
        _items = items;
    }

    public T Current => _position < _items.Length ? _items[_position] : default;

    object IEnumerator.Current => this.Current;

    public bool MoveNext()
    {
        if (_position < _items.Length - 1)
        {
            _position++;
            return true;
        }
        else
        {
            _position = _items.Length;
            return false;
        }
    }

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

    public void Dispose()
    {
        // Nothing to dispose
    }
}

4. Can you explain how to use the yield keyword in a method?

The yield keyword enables you to simplify the implementation of an enumerator, which is usually complex due to the need to keep track of the state across subsequent calls. Using yield return in a method allows you to return each item one at a time without needing to build a complete list first:

public IEnumerable<int> GetNumbers()
{
    yield return 1;
    yield return 2;
    yield return 3;
    // More yield returns...
}

With yield inside a method, the method generates a sequence of values on demand.

5. How does the 'foreach' loop work with IEnumerable?

The foreach loop internally uses the GetEnumerator() method to obtain an IEnumerator to iterate through the collection. It checks if there are any more items (MoveNext()) and retrieves the current item (Current) until no further items are available:

foreach (int number in myCollection)
{
    Console.WriteLine(number);
}

When the loop completes, the IEnumerator.Dispose() method is called if implemented.

6. What is the difference between IEnumerable and IEnumerable?

IEnumerable<T> is a generic interface that exposes an enumerator, which supports a simple iteration over a collection of a specific type (T). This helps in type safety and avoids unnecessary boxing/unboxing operations compared to IEnumerable:

public class MyCollection<T> : IEnumerable<T>
{
    private T[] _items;

    public IEnumerator<T> GetEnumerator()
    {
        foreach (var item in _items)
        {
            yield return item;
        }
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return this.GetEnumerator();
    }
}

Using IEnumerable<T> is generally preferable in C#.

7. Does IEnumerable load all items into memory at once or on-demand?

IEnumerable does not load all items into memory at once. Instead, it provides each element one at a time on-demand, which makes it suitable for dealing with large collections:

public IEnumerable<int> GenerateNumbers(int count)
{
    int i = 0;
    while (i < count)
    {
        yield return i++;
    }
}

This deferred execution can significantly reduce memory usage when handling large datasets.

8. Can you use LINQ methods with IEnumerable?

Absolutely, you can use LINQ (Language Integrated Query) methods with IEnumerable<T>:

var evenNumbers = myCollection.Where(x => x % 2 == 0).ToList();

// Or even better, when you want to avoid creating a List:
foreach (var number in myCollection.Where(x => x % 2 == 0))
{
    Console.WriteLine(number);
}

LINQ operations on IEnumerable<T> are also deferred, enhancing performance for large sets of data.

9. Are IEnumerable and IEnumerator useful outside of collections?

Yes, IEnumerable and IEnumerator can be useful in many other contexts. For instance, they may represent sequences that logically extend beyond traditional collections (e.g., infinite sequences) or are generated during runtime:

public IEnumerable<int> GenerateInfiniteSequence()
{
    int i = 0;
    while (true)
    {
        yield return i++;
    }
}

10. Should IEnumerator be disposed of and why?

If IEnumerator implements IDisposable, it is a good practice to call Dispose() after the iteration is complete, although the foreach loop automatically handles this when using IEnumerable<T>. Disposing an IEnumerator ensures that all resources (like file handles or network connections) used during enumeration are properly released:

IEnumerator<int> enumerator = myCollection.GetEnumerator();
try
{
    while (enumerator.MoveNext())
    {
        // Use enumerator.Current
    }
}
finally
{
    if (enumerator != null && enumerator is IDisposable disposable)
    {
        disposable.Dispose();
    }
}

Alternatively, using a using statement with IEnumerator ensures proper disposal:

You May Like This Related .NET Topic

Login to post a comment.