Who can foreach

Published on 09 August 2017

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 or System.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.

blog comments powered by Disqus