IEnumerable and IEnumerator
IEnumerator Interface
Elements in a collection are enumerated in a forward manner only.
public interface IEnumerator { bool MoveNext(); object Current { get; } void Reset(); }
MoveNext – advances the current element to the next position or returns false if there are no more elements in the collection.
Current – returns the element at the current position.
Reset – moves back to the start of the collection.
Collections do not implement enumerators, instead they provide enumerators via the interface IEnumerable.
public interface IEnumerable { IEnumerable GetEnumerator(); }
IEnumerable
Provides flexibility in that the iteration logic can be farmed off to another class.
Several consumers can enumerate the collection at once without interfering with each other.
IEnumerable can be thought of as IEnumeratorProvider.
string s = "Hello"; // Because string implements IEnumerable, we can call GetEnumerator(): IEnumerator rator = s.GetEnumerator(); while (rator.MoveNext()) { char c = (char) rator.Current; Console.Write (c + "."); } Console.WriteLine(); // Equivalent to: foreach (char c in s) Console.Write (c + ".");
IEnumerable<T> and IEnumerator<T>
IEnumerator and IEnumerable are nearly always implemented in conjunction with their extended generic versions.
public interface IEnumerator: IEnumerator, IDisposable { T Current { get; } } public interface IEnumerable : IEnumerable { IEnumerator GetEnumerator(); }
By defining typed version of Current and GetEnumerator, these interfaces strengthen static type safety and avoid the boxing overhead.
Arrays automatically implement IEnumerable<T>.
IEnumerable<T> and IDisposable
IEnumerator<T> inherits from IDisposable.
This allows enumerators to hold references to resources such as database connections to ensure that those resources are released when enumeration is complete.
foreach (var element in somethingEnumerable) { ... }
translates into:
using ( var rator = somethingEnumerable.GetEnumerator()) while (rator.MoveNext()) { var element = rator.Current; ... }
The using block ensures disposal.
IEnumerables = "Hello"; using (var rator = s.GetEnumerator()) while (rator.MoveNext()) { char c = (char) rator.Current; Console.Write (c + "."); }
Non Generic Interface
void Main() { Count("the quick brown fox".Split()); } public static int Count(IEnumerable e) { int count = 0; foreach (object element in e) { var subCollection = element as IEnumerable; if (subCollection != null) count += Count (subCollection); else count++; } return count; }
IEnumerable or IEnumerable<T> can be implemented for one of the reasons:
- To support the foreach statement.
- To inter-operate with anything expecting a standard collections.
- To meet the requirements of a more sophisticated collection interface.
- To support collection initializers.
To implement IEnumerable/IEnumerable<T>:
- If class is wrapping another collection, return the wrapped collection’s enumerator
- Using the iterator yield return
- By instantiating your own implementation
Using Iterator
void Main() { foreach (int element in new MyCollection()) Console.WriteLine (element); } public class MyCollection : IEnumerable { int[] data = { 1, 2, 3 }; public IEnumerator GetEnumerator() { foreach (int i in data) yield return i; } }
Using Iterator using Generic
void Main() { foreach (int element in new MyGenCollection()) Console.WriteLine (element); } public class MyGenCollection : IEnumerable<int> { int[] data = { 1, 2, 3 }; public IEnumeratorGetEnumerator() { foreach (int i in data) yield return i; } IEnumerator IEnumerable.GetEnumerator() // Explicit implementation { // keeps it hidden. return GetEnumerator(); } }
Using Iterator Method
The yield statement allows for an easier variation. Instead of writing a class the iteration logic can be moved into a method returning a generic IEnumerable<T> and let the compiler take care of the rest.
void Main() { foreach (int i in Test.GetSomeIntegers()) Console.WriteLine (i); } public class Test { public static IEnumerableGetSomeIntegers() { yield return 1; yield return 2; yield return 3; } }