Who can foreach

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.

Links

blog comments powered by Disqus