1

我有以下代码:

class Program
{
    static void Main(string[] args)
    {
        foreach (var item in GetEnumerable().Skip(100))
        {
            Console.WriteLine(item);
        }
    }
    static IEnumerable<int> GetEnumerable(int? page = null, int limit = 10)
    {
        var currentPage = page ?? 1;
        while (true)
        {
            Thread.Sleep(1000); // emulates slow retrieval of a bunch of results
            for (int i = limit * (currentPage - 1); i < limit * currentPage; i++)
            {
                yield return i;
            }
            currentPage++;
        }
    }
}

我希望能够.Skip(n)有效地跳过我不需要的结果。因此,例如,如果我使用Skip(100)并且每个请求检索 10 个项目,则应完全跳过前 10 个请求。

有没有我可以用来实现这一目标的模式?

4

5 回答 5

3

您可以创建自己的IEnumerable<int>类型并提供自己的实现Skip

public class PagedEnumerable : IEnumerable<int>
{
    private readonly int currentPage;
    private readonly int limit;

    public PagedEnumerable(int currentPage, int limit)
    {
        this.currentPage = currentPage;
        this.limit = limit;
    }

    public PagedEnumerable Skip(int count)
    {
        int pages = count / this.limit;
        return new PagedEnumerable(this.currentPage + pages, this.limit);
    }

    public IEnumerator<int> GetEnumerator()
    {
        int pageNo = this.currentPage;
        while (true)
        {
            Thread.Sleep(1000);
            for (int i = this.limit * (pageNo - 1); i < (this.limit * pageNo); i++)
            {
                yield return i;
            }
            pageNo++;
        }
    }

    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
    {
        return this.GetEnumerator();
    }
}

然后,您可以将您的替换GetEnumerable为:

static PagedEnumerable GetEnumerable(int? page = null, int limit = 10)
{
    var currentPage = page ?? 1;
    return new PagedEnumerable(currentPage, limit);
}
于 2012-11-14T22:43:49.060 回答
2

如果您想对“页面”进行延迟评估,则不应在 while 循环中加载它。

相反,您可以做的是返回一个 lambda 并从中返回页面,就像这样。

// return a list of funcs, where each one returns a loaded page
static IEnumerable<Func<int>> GetEnumerable(int? page = null, int limit = 10)
{
    var currentPage = page ?? 1;
    while (true)
    {
        for (int i = limit * (currentPage - 1); i < limit * currentPage; i++)
        {
            yield return () => {
               Thread.Sleep(1000);
               return i;
            };
        }
        currentPage++;
    }
}

从消费者线程中,您只需执行返回的函数即可获取页面。

foreach (var item in GetEnumarable().Skip(100).Take(10))
{
    Console.WriteLine(item());
}
于 2012-11-14T22:27:52.713 回答
1

这是我对此的看法。我使用 lambda 表达式以 Patrick 的想法为基础,但我已对其进行了修复,以便它仅在需要时评估每个批次,并且不超过一次。

static IEnumerable<Func<int>> GetEnumerable(int? page = null, int limit = 10)
{
    var currentPage = page ?? 1;
    while (true)
    {
        var thisPage = currentPage;
        List<int> thisPageResult = null;

        // Function that evaluates this batch and returns the result
        Func<List<int>> getPageResult = () =>
        {
            // only evaluate this batch once
            if (thisPageResult == null)
            {
                // slow retrieval of a bunch of results happens here
                Thread.Sleep(1000);
                // store the result for future calls
                thisPageResult = Enumerable.Range(limit * (thisPage - 1), limit).ToList();
            }
            return thisPageResult;
        };

        for (int i = 0; i < limit; i++)
        {
            var j = i;
            // lazy: evaluate the batch only if requested by client code
            yield return () => getPageResult()[j];
        }

        currentPage++;
    }
}

static void Main(string[] args)
{
    foreach (var func in GetEnumerable().Skip(100).Take(10))
    {
        Console.WriteLine(func());
    }
}
于 2012-11-15T00:40:53.847 回答
0

让它成为方法中的第一个参数,不要给它一个默认值。

于 2012-11-14T22:14:31.047 回答
0

希望有人发现这种方法有帮助。.NET 中的通用 Lazy 类适用于这种情况。

//Enumerable over all of the pages you want to possibly retrieve from:
IEnumerable<Lazy<Page>> pages = Enumerable
    .Range(0, 5)
    .Select(i => new Lazy<Page>(() => LoadPage(i)));
//Now if each page contains 10 items, and you want to skip the first
//35 items (and thus not load the first 3 pages), do this:
var items = pages
    .SelectMany(page => Enumerable
        .Range(0, 10)
        .Select(i => () => page.Value.GetItem(i)))
    .Skip(35) //any combination of Take, Skip, etc. could go here
    .Select(itemGetter => itemGetter())
    .ToList();
于 2012-11-16T17:28:42.617 回答