views:

118

answers:

2

I need an easy way to iterate over multiple collections without actually merging them, and I couldn't find anything built into .NET that looks like it does that. It feels like this should be a somewhat common situation. I don't want to reinvent the wheel. Is there anything built in that does something like this:

public class MultiCollectionEnumerable<T> : IEnumerable<T>
{
    private MultiCollectionEnumerator<T> enumerator;
    public MultiCollectionEnumerable(params IEnumerable<T>[] collections)
    {
        enumerator = new MultiCollectionEnumerator<T>(collections);
    }

    public IEnumerator<T> GetEnumerator()
    {
        enumerator.Reset();
        return enumerator;
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        enumerator.Reset();
        return enumerator;
    }


    private class MultiCollectionEnumerator<T> : IEnumerator<T>
    {
        private IEnumerable<T>[] collections;
        private int currentIndex;
        private IEnumerator<T> currentEnumerator;

        public MultiCollectionEnumerator(IEnumerable<T>[] collections)
        {
            this.collections = collections;
            this.currentIndex = -1;
        }

        public T Current
        {
            get
            {
                if (currentEnumerator != null)
                    return currentEnumerator.Current;
                else
                    return default(T);
            }
        }

        public void Dispose()
        {
            if (currentEnumerator != null)
                currentEnumerator.Dispose();
        }

        object IEnumerator.Current
        {
            get
            {
                return Current;
            }
        }

        public bool MoveNext()
        {
            if (currentIndex >= collections.Length)
                return false;
            if (currentIndex < 0)
            {
                currentIndex = 0;
                if (collections.Length > 0)
                    currentEnumerator = collections[0].GetEnumerator();
                else
                    return false;
            }
            while (!currentEnumerator.MoveNext())
            {
                currentEnumerator.Dispose();
                currentEnumerator = null;

                currentIndex++;
                if (currentIndex >= collections.Length)
                    return false;
                currentEnumerator = collections[currentIndex].GetEnumerator();
            }
            return true;
        }

        public void Reset()
        {
            if (currentEnumerator != null)
            {
                currentEnumerator.Dispose();
                currentEnumerator = null;
            }
            this.currentIndex = -1;
        }
    }

}
+13  A: 

Try the SelectMany extension method added in 3.5.

IEnumerable<IEnumerable<int>> e = ...;
foreach ( int cur in e.SelectMany(x => x)) {
  Console.WriteLine(cur);
}

The code SelectMany(x => x) has the effect of flattening a collection of collections into a single collection. This is done in a lazy fashion and allows for straight forward processing as shown above.

If you only have C# 2.0 available, you can use an iterator to achieve the same results.

public static IEnumerable<T> Flatten<T>(IEnumerable<IEnumerable<T>> enumerable) {
  foreach ( var inner in enumerable ) {
    foreach ( var value in inner ) {
      yield return value;
    }
  }
}
JaredPar
I could be wrong, but I don't think the `var` keyword is available in C# 2.0.
Dan Tao
@Dan You are right, no var keyword in C# 2.0. It would be shorter to use the type parameter T anyway. Easy fix.
Jamie Penney
It's not available in the C# 2.0 compiler, but you can use VS 2008 or 2010 to target .NET 2.0 with code that uses the `var` keyword, which gets resolved to an actual type during compilation, leaving the runtime none the wiser.
Joel Mueller
"var" is available in Visual Studio 2008 and later, regardless of the .NET version targeted.
Bryce Wagner
@Joel: True... for a time where I work we were still on VS 2005, so I generally try to cater "If you're still on C# 2.0 / .NET 2.0..." remarks to those in that particular boat. Regardless, as Jamie pointed out, totally trivial to change `var` to `T`. (These comments are mainly for the benefit of the OP [**EDIT**: ...who apparently already knew all this!].)
Dan Tao
For my situation, the yield return solution works even better. I was quite aware of yield return, and I've used it before, but infrequently enough I forget it exists. It makes it possible to merge IEnumerable<T1> and IEnumerable<T2> as IEnumerable<T> where T1 and T2 both descend from T, without having to upgrade to .NET 4.
Bryce Wagner
+8  A: 

Just use the Enumerable.Concat() extension method to "concatenate" two IEnumerables. Don't worry, it doesn't actually copy them into a single array (as you might infer from the name), it simply allows you to enumerate over them all as if they were one IEnumerable.

If you have more than two then Enumerable.SelectMany() would be better.

Evgeny
`var list = list1.Concat(list2).Concat(ienumerable3).Concat(array4);` is a nice concise way to do this, and the bonus is that you can do it over different collection types, as long as they all have the same type parameter.
Jamie Penney
Or use Union (http://msdn.microsoft.com/en-us/library/bb341731.aspx) if you need to discourage duplicates...
Reddog