C# > Advanced C# > Collections and Generics > IEnumerable<T> and IEnumerator<T>
Custom Collection with IEnumerable and IEnumerator
This snippet demonstrates how to create a custom collection in C# that implements The example creates a simple IEnumerable<T>
and provides its own IEnumerator<T>
implementation. This allows you to iterate over the collection using foreach
or other mechanisms that rely on the IEnumerable
interface.CustomList<T>
that stores a fixed-size array of items. It provides a custom enumerator called CustomListEnumerator<T>
that handles the iteration logic.
Code Example
The The The CustomList<T>
class implements IEnumerable<T>
, which requires the implementation of GetEnumerator()
. This method returns an instance of the custom enumerator, CustomListEnumerator<T>
.CustomListEnumerator<T>
class implements IEnumerator<T>
. It maintains the current index and the current item during iteration. The MoveNext()
method advances the enumerator to the next item in the collection, and the Current
property returns the current item.Example
class demonstrates how to use the CustomList<T>
class in a foreach
loop. The loop automatically calls GetEnumerator()
and uses the MoveNext()
and Current
properties to iterate through the collection.
using System;
using System.Collections;
using System.Collections.Generic;
public class CustomList<T> : IEnumerable<T>
{
private T[] _items;
private int _currentIndex = -1;
public CustomList(int size)
{
_items = new T[size];
}
public void Add(T item, int index)
{
if (index >= 0 && index < _items.Length)
{
_items[index] = item;
}
}
public T GetItem(int index)
{
if (index >= 0 && index < _items.Length)
{
return _items[index];
}
return default(T);
}
public IEnumerator<T> GetEnumerator()
{
return new CustomListEnumerator<T>(this);
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
private class CustomListEnumerator<T> : IEnumerator<T>
{
private CustomList<T> _list;
private int _index;
private T _current;
public CustomListEnumerator(CustomList<T> list)
{
_list = list;
_index = -1;
_current = default(T);
}
public T Current
{
get { return _current; }
}
object IEnumerator.Current
{
get { return Current; }
}
public void Dispose() { }
public bool MoveNext()
{
_index++;
if (_index >= 0 && _index < _list._items.Length)
{
_current = _list._items[_index];
return true;
}
else
{
return false;
}
}
public void Reset()
{
_index = -1;
_current = default(T);
}
}
}
public class Example
{
public static void Main(string[] args)
{
CustomList<string> myList = new CustomList<string>(3);
myList.Add("Item 1", 0);
myList.Add("Item 2", 1);
myList.Add("Item 3", 2);
foreach (string item in myList)
{
Console.WriteLine(item);
}
}
}
Concepts Behind the Snippet
IEnumerable<T>: Represents a sequence of objects that can be iterated over. IEnumerator<T>: Provides the functionality to iterate through a collection, providing access to each element one at a time. Implementing these interfaces allows your custom collections to be used with foreach
loops and LINQ queries.
Real-Life Use Case
Consider a scenario where you have a data structure that doesn't directly inherit from standard collections like List<T>
or Array
, but you still need to provide a way to iterate over its elements. For example, you might have a custom tree structure, a graph, or a collection that loads data lazily from a file. Implementing IEnumerable<T>
and IEnumerator<T>
allows you to expose the data in a way that can be easily consumed by other parts of your application, using standard iteration patterns.
Best Practices
Dispose Resources: If your enumerator uses any resources (e.g., file handles, network connections), implement the Thread Safety: Be aware of thread safety when implementing Consider Yield Return: For simple iterations, using the IDisposable
interface to release those resources when the enumeration is complete.IEnumerator
. If the underlying collection can be modified by multiple threads, you'll need to implement appropriate locking mechanisms to prevent race conditions.yield return
statement within a method that returns IEnumerable<T>
can greatly simplify the implementation of the enumerator.
Interview Tip
Be prepared to explain the difference between IEnumerable
and IEnumerator
. IEnumerable
represents the collection, while IEnumerator
provides the mechanism for traversing that collection. Also, understand how foreach
internally uses these interfaces.
When to use them
Use IEnumerable<T>
and IEnumerator<T>
when you need to create custom collections with specific iteration logic, or when you need to expose an existing data structure in a way that allows for easy iteration using foreach
loops.
Memory footprint
The memory footprint depends on the underlying data structure. The IEnumerator
itself generally has a small memory footprint, as it only needs to track the current position. However, if the enumeration involves loading large amounts of data, the overall memory usage will be higher.
Alternatives
Using Using Existing Collections: Whenever possible, leverage existing collection classes (e.g., yield return
: For simple scenarios, using yield return
is a more concise way to implement IEnumerable<T>
. This avoids the need to create a separate enumerator class.List<T>
, Array
) instead of creating custom collections from scratch.
Pros
Customizable Iteration: Provides full control over how the collection is iterated. Integration with Lazy Evaluation: Enables lazy loading and processing of data during iteration.foreach
: Allows your custom collections to be used seamlessly with foreach
loops.
Cons
Increased Complexity: Implementing Potential for Errors: Incorrectly implemented enumerators can lead to unexpected behavior, such as infinite loops or incorrect data access.IEnumerator
manually can be more complex than using yield return
or existing collections.
FAQ
-
What is the difference between IEnumerable and IEnumerator?
IEnumerable represents a collection that can be iterated, while IEnumerator provides the mechanism to iterate through the collection, keeping track of the current position. -
Why should I implement IEnumerable and IEnumerator?
Implementing these interfaces allows your custom collections to be used with 'foreach' loops and LINQ queries, providing a standard way to iterate over your data. -
Can I use 'yield return' instead of implementing IEnumerator?
Yes, for simple scenarios, 'yield return' provides a more concise way to implement IEnumerable. However, for more complex iteration logic, implementing IEnumerator might be necessary.