Bartosz Wójtowicz
Bartosz Wójtowicz

Reputation: 1401

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.

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;
    }
}

Upvotes: 8

Views: 1631

Answers (3)

Corentin Pane
Corentin Pane

Reputation: 4933

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.

Upvotes: 7

Rich_Rich
Rich_Rich

Reputation: 439

As the answer of @corentin-pane has too many pending edits, I will share the extension methods I ended up implementing as a full new answer

public static class ExtraLinqExtensions
{
    public static (T, IEnumerable<T>) GetFirstAndRemainder<T>(this IEnumerable<T> sequence)
    {
        using var enumerator = sequence.GetEnumerator();
        return enumerator.MoveNext()
            ? (enumerator.Current, enumerator.AsEnumerable())
            : (default, Enumerable.Empty<T>());
    }

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

Where the most notable change is the check for whether the enumerator succeeded in doing a MoveNext().

Upvotes: 2

Good Night Nerd Pride
Good Night Nerd Pride

Reputation: 8442

There is also the possibility to do cheeky deconstruction like so:

var (x, xs) = new int?[] { 1, 2, 3 };
// x = 1, xs = 2, 3

All you need is to implement Deconstruct() on IEnumerable<T>. Based on the implementations from previous answers:

public static class Ext {
    public static void Deconstruct<T>(this IEnumerable<T> source, out T? first, out IEnumerable<T> tail) {
        using var e = source.GetEnumerator();
        (first, tail) = e.MoveNext()
            ? (e.Current, e.AsEnumerable())
            : (default, Enumerable.Empty<T>());
    }

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

If you feel particularly funny you can extend that approach to do things like this:

var (fst, snd, trd, rest) = new int?[] { 1, 2, 3, 4, 5 };
public static class Ext {
    // ...

    public static void Deconstruct<T>(this IEnumerable<T> source, out T? first, out T? second, out IEnumerable<T> tail) {
        using var e = source.GetEnumerator();
        if (e.MoveNext())
            (first, (second, tail)) = (e.Current, e.AsEnumerable());
        else
            (first, second, tail) = (default, default, Enumerable.Empty<T>());
    }

    public static void Deconstruct<T>(this IEnumerable<T> source, out T? first, out T? second, out T? third, out IEnumerable<T> tail) {
        using var e = source.GetEnumerator();
        if (e.MoveNext())
            (first, (second, third, tail)) = (e.Current, e.AsEnumerable());
        else
            (first, second, third, tail) = (default, default, default, Enumerable.Empty<T>());
    }
}

Upvotes: 2

Related Questions