How to Use Custom Collections in your C# Application with IEnumerable and IEnumerator
Sometimes, the available collections of C# will not be good enough for a project. From the implementation of IEnumerable and IEnumerator you can develop your custom collections supporting iteration via foreach :.
In this blog post, you will learn how to create your custom collection step by step and implement the IEnumerable and IEnumerator interfaces.
Why Use IEnumerable and IEnumerator?
IEnumerable allows iteration of a collection with a foreach statement.
IEnumerator is the abstraction providing the ability to iterate through the collection's elements.
Implementing these interfaces gives your custom collection an experience as smooth as one of the built-in collections.
Implementation of a Step-by-Step Procedure
Let's implement a simple custom collection named MyCollection that contains integers and supports iteration.
1. The Class Definition of MyCollection
Let's define a class that implements IEnumerable<int>.
using System;
using System.Collections;
using System.Collections.Generic;
public class MyCollection : IEnumerable<int>
{
private List<int> _items = new List<int>();
// Add method to add items to the collection
public void Add(int item)
{
_items.Add(item);
}
// Implement the GetEnumerator method
public IEnumerator<int> GetEnumerator()
return new MyEnumerator(_items);
}
// Explicit implementation of IEnumerable.GetEnumerator
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
2. Define the MyEnumerator Class
Now, we'll let the IEnumerator<int> interface in one class handle the traversal separately,
public class MyEnumerator: IEnumerator<int>
{
private readonly List<int>_items;
private int_position = - 1; // Before first element
public MyEnumerator(List<int> items )
{
_items = items;
}
// Current property to return the current element
public int Current => _items[_position];
object IEnumerator.Current
{ get { return Current; } }
// MoveNext method to advance to the next element
public bool MoveNext()
{
_position++;
return _position < _items.Count;
}
// Reset method to reset the position
public void Reset()
_position = -1;
}
// Dispose method (no unmanaged resources, so nothing to dispose)
public void Dispose()
{
}
}
Using the Custom Collection
Let's use our MyCollection class and MyEnumerator class in a simple program now that we have them set up.
class Program
static void Main()
{
MyCollection collection = new MyCollection();
collection.Add(1);
collection.Add(2);
collection.Add(3);
collection.Add(4);
foreach (int item in collection)
{
Console.WriteLine(item);
}
}
}
Output:
1
2
3
4
Breakdown of the Code
1. MyCollection Class
Implements IEnumerable<int>, allowing it to be used in foreach loops.
The Add method lets you add integers to the collection.
The GetEnumerator method returns an instance of MyEnumerator for iteration.
2. MyEnumerator Class
Implements IEnumerator<int>, which manages the iteration state.
MoveNext advances the iterator and returns false when the end is reached.
Reset resets the iterator to the start.
Current returns the current item during iteration.
Best Practices and Tips
1. Dispose Method: If your enumerator uses unmanaged resources, ensure Dispose properly cleans them up.
2. Yield Return: For simpler collections, consider using yield return in the GetEnumerator method instead of creating a separate enumerator class.
3. Thread-Safety: Be careful if your collection is accessed by multiple threads.
Using yield return (Alternative Implementation)
Here is an easy version of MyCollection with yield return:
public class MyCollection : IEnumerable<int>
{
private List<int> _items = new List<int>();
public void Add(int item)
_items.Add(item);
}
public IEnumerator<int> GetEnumerator()
foreach (var item in _items)
{
yield return item;
}
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
This approach avoids creating a separate IEnumerator class, making the code cleaner for simpler cases.
Conclusion
By implementing IEnumerable and IEnumerator, you can create robust, customizable collections in C#. Whether you need full control over the iteration process or a simplified version with yield return, these interfaces provide flexibility and power.
Happy coding!