4

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;
    }
}
Bartosz Wójtowicz
  • 1,321
  • 10
  • 18
  • Not an answer to your question, but you might want to consider a threadsafe container like `ConcurrentBag` –  Dec 03 '19 at 16:33
  • 1
    Shouldn't that be `first = enumerable.First();` or maybe `first = enumerable.FirstOrDefault()`? `Take` returns an `IEnumerable` which cannot be assigned to `first`. – juharr Dec 03 '19 at 16:47
  • You could just do a non-lazy approach by creating a `List` and adding the values to that and return it. – juharr Dec 03 '19 at 16:50
  • @juharr, you're right, I added the ```FirstOrDefault()```. Creating a ```List``` and returning it when it's populated would block the updates, whereas I need to show them as they arrive one by one. – Bartosz Wójtowicz Dec 04 '19 at 11:09

3 Answers3

5

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>.

Corentin Pane
  • 4,794
  • 1
  • 12
  • 29
2

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().

Rich_Rich
  • 427
  • 3
  • 15
  • Return type should be `(T?, IEnumerable)` and why not return `(default, Enumerable.Empty())` in the empty case? – Good Night Nerd Pride Jan 10 '23 at 16:42
  • It can happen that `default(T) != null` so the return type cannot be `T?`. I think having `T` instead of `T?` has more ease of use. The empty enumerable suggestion I agree with though. – Rich_Rich Jan 11 '23 at 10:47
  • Yeah I forgot about that weird inconsistency. To enforce a "true" `null` when `T` is e.g. `int` one would have to implement the method twice: once with `where T: struct` and again with `where T: class`. But both implementations would have to be in two different extension classes too, or the compiler will object, because to it both look the same as generic constraints are not part of the method signature... – Good Night Nerd Pride Jan 11 '23 at 12:33
2

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>());
    }
}
Good Night Nerd Pride
  • 8,245
  • 4
  • 49
  • 65