I've got an enumerable that contains responses from a service call that come in gradually.
ToList
on the enumerable as that would block until all responses are received instead of listing them as they come. 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;
}
}
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
.