Search code examples
c#ienumerableenumerateenumerator

Get the first item of an IEnumerable and return the rest as IEnumerable, iterating through only once


I've got an enumerable that contains responses from a service call that come in gradually.

  • I can't do ToList on the enumerable as that would block until all responses are received instead of listing them as they come.
  • I also can't iterate twice as that would trigger another service call.

How to get the first element in the enumerable and return the continuation of the enumerable? I can't use an iterator method as I get a compilation error:

Iterators cannot have ref, in or out parameters.

I've tried this code:

public IEnumerable<object> GetFirstAndRemainder(IEnumerable<object> enumerable, out object first)
{
      first = enumerable.Take(1).FirstOrDefault();
      return enumerable.Skip(1);  // Second interation - unexceptable
}


// This one has a compilation error: Iterators cannot have ref, in or out parameters
public IEnumerable<object> GetFirstAndRemainder2(IEnumerable<object> enumerable, out object first)
{
    var enumerator = enumerable.GetEnumerator();
    enumerator.MoveNext();
    first = enumerator.Current;
    while (enumerator.MoveNext())
    {
        yield return enumerator.Current;
    }
}

Solution

  • Instead of using an out parameter, you can use ValueTuple<T1, T2> (as of C# 7.0, documented here) to return two elements: the first item of the IEnumerable<T>, and the remainder as another IEnumerable<T>.

    using System.Linq;
    
    class Program {
        static void Main(string[] args) {
            (int first, IEnumerable<int> remainder) = GetFirstAndRemainder(Enumerable.Range(1, 5));
            // first = 1
            // remainder yields (2, 3, 4, 5)
        }
    
        // Returns the first item and the remainders as an IEnumerable
        static (T, IEnumerable<T>) GetFirstAndRemainder<T>(IEnumerable<T> sequence) {
            var enumerator = sequence.GetEnumerator();
            enumerator.MoveNext();
            return (enumerator.Current, enumerator.AsEnumerable());
        }
    }
    

    You also need to convert from an IEnumerator to an IEnumerable which I did with an extension method:

    static class Extensions {
        public static IEnumerable<T> AsEnumerable<T>(this IEnumerator<T> enumerator) {
            while (enumerator.MoveNext()) {
                yield return enumerator.Current;
            }
        }
    }
    

    Note that due to your requirements, iterating once over the remainder will exhaust it even though it has the type IEnumerable<T>.


    If you need to ensure the disposal of the enumerator, then you should probably ditch these wrapping methods entirely and directly work from the IEnumerator<T> yourself. It'll save you some typing and make it more explicit that there is only one enumeration.


    It's an XY problem: from the description you give, it looks like you really need IAsyncEnumerable to be able to process items one-by-one as they come in gradually, which is probably what you're trying to do with GetFirstAndRemainder.