Reputation: 1401
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;
}
}
Upvotes: 8
Views: 1631
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
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
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