假设我们有IEnumerable<T> stuff;
有没有一种简洁的方法来获取n个元素,然后是第一个元素,而不是重新评估?
示例代码:
stuff.Take(10);
stuff.Skip(10).Take(20); // re-evaluates stuff
我在想的可能是这个(不是工作代码)
var it = stuff.GetEnumerator();
it.Take(10);
it.Take(20);
编辑以增加难度并澄清我想要完成的事情的复杂性:我想在Take之后继续查询,即
it.Take(10);
var cont = it.Select(Mutate);
cont.Take(20);
cont = cont.Where(Filter);
cont.Take(5);
答案 0 :(得分:5)
您可以使用Microsoft推出的Publish
NuGet包中的System.Interactive
扩展方法来完成此操作。这是一个很棒的库,可以提供一些“缺失”的LINQ函数。从文档中,Publish
方法:
创建一个带有源序列视图的缓冲区,使每个枚举器从缓冲区中的当前索引获得对序列其余部分的访问。
即。它允许您部分枚举一个序列,下次枚举序列时,您将获取前一个枚举停止的位置。
var publishedSource = stuff.Publish();
var firstTenItems = publishedSource.Take(10).ToArray();
var nextTwentyTransformedItems = publishedSource.Take(20).Select(Mutate).ToArray();
// How you apply 'Where' depends on what you want to achieve.
// This returns the next 5 items that match the filter but if there are less
// than 5 items that match the filter you could end up enumerating the
// entire remainder of the sequence.
var nextFiveFilteredItems = publishedSource.Where(Filter).Take(5).ToArray();
// This enumerates _only_ the next 5 items and yields any that match the filter.
var nextOfFiveItemsThatPassFilter = publishedSource.Take(5).Where(Filter).ToArray()
答案 1 :(得分:2)
如果您只想为IEnumerable
创建一个包装器来处理附加的LINQ并通过源代码,请使用此类和扩展名:
public static class EnumerableOnceExt {
public static EnumerableOnce<IEnumerable<T>, T> EnumerableOnce<T>(this IEnumerable<T> src) => new EnumerableOnce<IEnumerable<T>, T>(src);
}
public class EnumerableOnce<T, V> : IEnumerable<V>, IDisposable where T : IEnumerable<V> {
EnumeratorOnce<V> onceEnum;
public EnumerableOnce(T src) {
onceEnum = new EnumeratorOnce<V>(src.GetEnumerator());
}
public IEnumerator<V> GetEnumerator() {
return onceEnum;
}
IEnumerator IEnumerable.GetEnumerator() {
return onceEnum;
}
public void DoSkip(int n) {
while (n > 0 && onceEnum.MoveNext())
--n;
}
public void DoTake(int n) {
while (n > 0 && onceEnum.MoveNext())
--n;
}
#region IDisposable Support
private bool disposedValue = false; // To detect redundant calls
protected virtual void Dispose(bool disposing) {
if (!disposedValue) {
if (disposing) {
onceEnum.ActuallyDispose();
}
disposedValue = true;
}
}
// This code added to correctly implement the disposable pattern.
public void Dispose() {
Dispose(true);
}
#endregion
}
public class EnumeratorOnce<V> : IEnumerator<V> {
IEnumerator<V> origEnum;
public EnumeratorOnce(IEnumerator<V> src) {
origEnum = src;
}
public V Current => origEnum.Current;
object IEnumerator.Current => origEnum.Current;
public bool MoveNext() => origEnum.MoveNext();
public void Reset() {
origEnum.Reset();
}
public void ActuallyDispose() {
origEnum.Dispose();
}
#region IDisposable Support
protected virtual void Dispose(bool disposing) {
// don't allow disposing early
}
// This code added to correctly implement the disposable pattern.
public void Dispose() {
Dispose(true);
}
#endregion
}
现在,只要您执行枚举,只要您调用EnumerableOnce()
来包装源代码,您的示例代码就会起作用:
var it1 = it.EnumerableOnce();
it1.Take(10).ToList();
var @continue = it1.Select(Mutate);
@continue.Take(20).ToList();
@continue = @continue.Where(Filter);
@continue.Take(5).ToList();
您还可以向EnumerableOnce
添加新方法:
public void DoSkip(int n) {
while (n > 0 && srcEnum.MoveNext())
--n;
}
public void DoTake(int n) {
while (n > 0 && srcEnum.MoveNext())
--n;
}
并打电话给他们:
var it1 = it.EnumerableOnce();
it1.DoTake(10);
var @continue = it1.Select(Mutate);
@continue.DoSkip(20);
@continue = @continue.Where(Filter);
@continue.DoTake(5);