Monday, September 15, 2014

Possible Multiple Enumeration Warning

Someone recently asked me what the "Possible Multiple Enumeration" warning means. The IEnumerable interface only exposes a single method, GetEnumerator. This means that every and every time we want to traverse the enumerable we have to start at the beginning and iterate the entire enumeration again.

public interface IEnumerable<out T> : IEnumerable
{
    IEnumerator<T> GetEnumerator();
}
 
public interface IEnumerable
{
    IEnumerator GetEnumerator();
}

Whenever you see the Possible Multiple Enumeration warning the compiler is trying to tell you that your code my be sub-optimal at run time because it will have to completely traverse the enumerable multiple times.

More importantly, the compiler can not be sure what that enumeration will entail!

Why could that be bad?

With a collection we know what the contents and implementation of the enumerable are, and we are able to know the run time implications of iteration over that collection. However an IEnumerable is an abstraction and not a guaranteed implementation, meaning that it may represent a very inefficient enumeration.

For example, an object relational mapping (ORM) framework may expose an IEnumerable that loads it's items from a database on each iteration. Other IEnumerables may be computing complex and CPU intensive operations during iteration. The simple fact is that when your code is iterating over an IEnumerable you just can not be sure what is actually happening behind the interface.

This is not necessarily a bad thing, but that uncertainty does merit a warning.

Enumerable Demo

While I do recommend that you heed the warning and be careful about what your code is enumerating, the best defense against such things is always knowledge! Try to understand what your enumerators are doing and when.

Here is an example that shows the difference between iterating over an IEnumerable vs a List.

public class PossibleMultipleEnumerationTests
{
    public readonly StringBuilder Log = new StringBuilder();
 
    public IEnumerable<int> GetEnumerable()
    {
        Log.Append("1");
        yield return 1;
        Log.Append("2");
        yield return 2;
        Log.Append("3");
    }
 
    [Fact]
    public void Enumerable()
    {
        Log.Append("A");
        var enumerable = GetEnumerable();
        Log.Append("B");
 
        foreach (var c in enumerable)
            Log.Append("C" + c);
 
        foreach (var d in enumerable)
            Log.Append("D" + d);
 
        Log.Append("E");
 
        var log = Log.ToString();
        Assert.Equal("AB1C12C231D12D23E", log);
    }
 
    [Fact]
    public void List()
    {
        Log.Append("A");
        var enumerable = GetEnumerable();
        
        Log.Append("B");
        var list = enumerable.ToList();
 
        foreach (var c in list)
            Log.Append("C" + c);
 
        foreach (var d in list)
            Log.Append("D" + d);
 
        Log.Append("E");
 
        var log = Log.ToString();
        Assert.Equal("AB123C1C2D1D2E", log);
    }
}

Enjoy,
Tom

No comments:

Post a Comment

Real Time Web Analytics