LINQ继续Take后继续

时间:2017-12-26 20:44:55

标签: c# linq

假设我们有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);

2 个答案:

答案 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);