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.IEnumerable
orSystem.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.IEnumerable
interface 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.