LINQ继续Take后继续

Man*_*ert 10 c# linq

说我们有一个 IEnumerable<T> stuff;

是否有一种简洁的方法来获取n个元素,然后是第一个元素,而不是重新评估?

示例代码:

stuff.Take(10);
stuff.Skip(10).Take(20); // re-evaluates stuff
Run Code Online (Sandbox Code Playgroud)

我在想的可能是这个(不是工作代码)

var it = stuff.GetEnumerator();
it.Take(10);
it.Take(20);
Run Code Online (Sandbox Code Playgroud)

编辑以增加难度并澄清我想要完成的复杂性:我想在Take之后继续查询,即

it.Take(10);
var cont = it.Select(Mutate);
cont.Take(20);
cont = cont.Where(Filter);
cont.Take(5);
Run Code Online (Sandbox Code Playgroud)

Jas*_*oyd 6

您可以使用Microsoft提供PublishSystem.InteractiveNuGet包中的扩展方法来完成此操作.这是一个很棒的库,可以提供一些"缺失"的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()
Run Code Online (Sandbox Code Playgroud)

  • @Servy我觉得你误解了这个解决方案.这不会重复遍历序列,调用"Publish"可以防止这种情况发生. (2认同)

Net*_*age 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
}
Run Code Online (Sandbox Code Playgroud)

现在,如果您调用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();
Run Code Online (Sandbox Code Playgroud)

您还可以添加新方法EnumerableOnce

public void DoSkip(int n) {
    while (n > 0 && srcEnum.MoveNext())
    --n;
}

public void DoTake(int n) {
    while (n > 0 && srcEnum.MoveNext())
        --n;
}
Run Code Online (Sandbox Code Playgroud)

并称呼他们为:

var it1 = it.EnumerableOnce();
it1.DoTake(10);
var @continue = it1.Select(Mutate);
@continue.DoSkip(20);
@continue = @continue.Where(Filter);
@continue.DoTake(5);
Run Code Online (Sandbox Code Playgroud)

  • 您**必须**记住,除非您执行某些操作,否则 LINQ 操作不会执行;例如 `Take()` 实际上并不接受任何东西,它只是在管道中创建一个条件来限制结果_一旦它们被枚举_。如果不进行枚举,则不会发生任何事情,因此需要“ToList()”或“DoTake()”。 (2认同)