如何从IEnumerable中有效地跳过项目,偶尔会产生非常慢的请求?

时间:2012-11-14 22:11:04

标签: c# .net linq

我有以下代码:

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个请求。

我可以用一种模式来实现这个目标吗?

5 个答案:

答案 0 :(得分: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);
}

答案 1 :(得分: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());
}

答案 2 :(得分: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());
    }
}

答案 3 :(得分:0)

使其成为方法中的第一个参数,并且不给它一个默认值。

答案 4 :(得分: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();