我有一个枚举对象,其中包含来自逐渐传入的服务调用的响应。
- 我无法对可枚举的内容进行
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()
。