获取 IEnumerable 的第一项,并将其余项作为 IEnumerable 返回,仅循环访问一次



我有一个枚举对象,其中包含来自逐渐传入的服务调用的响应。

  • 我无法对可枚举的内容进行ToList,因为这会在收到所有响应之前阻止,而不是在收到它们时列出它们。
  • 我也不能迭代两次,因为这会触发另一个服务呼叫。

如何获取可枚举元素中的第一个元素并返回可枚举的延续?我无法使用迭代器方法,因为我收到编译错误:

迭代器不能有 ref、in 或 out 参数。

我试过这段代码:

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

您可以使用ValueTuple<T1, T2>(从此处记录的 C# 7.0 开始(返回两个元素,而不是使用out参数:IEnumerable<T>的第一项,其余的作为另一个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());
}
}

您还需要从IEnumerator转换为我使用扩展方法所做的IEnumerable

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

请注意,由于您的要求,在remainder上迭代一次会耗尽它,即使它的类型为IEnumerable<T>

也有可能像这样厚颜无耻地解构:

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

您所需要的只是在IEnumerable<T>上实施Deconstruct()。基于先前答案的实现:

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

如果你觉得特别有趣,你可以扩展这种方法来做这样的事情:

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

由于 @corentin-pane 的答案有太多待处理的编辑,我将分享我最终实现的扩展方法作为全新的答案

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

其中最显着的变化是检查枚举器是否成功执行MoveNext()

最新更新