This post is about popular question «What collection can be used in foearch statement?». If you think that correct answer is IEnumerable, read this post ?
First of all let’s read docs from Microsoft:
The foreach statement repeats a group of embedded statements for each element in an array or an object collection that implements the
System.Collections.IEnumerableorSystem.Collections.Generic.IEnumerable<T>interface.
These interfaces have only one method GetEnumerator that returns IEnumerator or IEnumerator<T>. IEnumerator interface describes iteration over collection. So, we can imagine that
var array = new[] { 1, 2, 3 };
foreach (var i in array)
{
Console.WriteLine(i);
}
is equal to:
var array = new[] { 1, 2, 3 };
var enumerator = array.GetEnumerator();
try
{
while (enumerator.MoveNext())
{
var element = enumerator.Current;
Console.WriteLine(element);
}
}
finally
{
(enumerator as IDisposable)?.Dispose();
}
We will get the same result.
There is more interesting document that describes foreach statement behavior — official specification:
A type C is said to be a collection type if it implements the
System.Collections.IEnumerableinterface or implements the collection pattern by meeting all of the following criteria:
- C contains a public instance method with the signature GetEnumerator() that returns a struct-type, class-type, or interface-type, which is called E in the following text.
- E contains a public instance method with the signature MoveNext() and the return type bool.
- E contains a public instance property named Current that permits reading the current value. The type of this property is said to be the element type of the collection type.
All we need — create class that implements the collection pattern:
public class TestCollection<T>
{
private readonly List<T> _list = new List<T>();
public TestEnumerator<T> GetEnumerator()
{
return new TestEnumerator<T>(_list);
}
public void Add(T i)
{
_list.Add(i);
}
}
public class TestEnumerator<T> : IDisposable
{
private readonly List<T> _list;
private int _index = -1;
public TestEnumerator(List<T> list)
{
_list = list;
}
public T Current => _list[_index];
public bool MoveNext()
{
_index++;
if (_index < _list.Count)
{
return true;
}
return false;
}
public void Dispose()
{
}
}
I use List<T> as internal collection, but TestCollection<T> does not implement IEnumerable.
First, check without foreach:
var collection = new TestCollection<int>();
collection.Add(1);
collection.Add(2);
collection.Add(3);
var enumerator = collection.GetEnumerator();
try
{
while (enumerator.MoveNext())
{
var element = enumerator.Current;
Console.WriteLine(element);
}
}
finally
{
(enumerator as IDisposable)?.Dispose();
}
And collapse it to foreach:
var collection = new TestCollection<int>();
collection.Add(1);
collection.Add(2);
collection.Add(3);
foreach (var i in collection)
{
Console.WriteLine(i);
}
Magic! Collection does not implement IEnumerable but can be used in foreach statement.