C# 如何有效地跳过IEnumerable中偶尔发出非常慢请求的项?

C# 如何有效地跳过IEnumerable中偶尔发出非常慢请求的项?,c#,.net,linq,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,

我有以下代码:

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++;
        }
    }
}
我希望能够使用.Skipn有效地跳过我不需要的结果。例如,如果我使用Skip100,并且每个请求检索10个项目,那么前10个请求应该被完全跳过


有没有一种模式可以用来实现这一点?

将其作为方法中的第一个参数,不要给它一个默认值。

将其作为方法中的第一个参数,不要给它一个默认值。

如果要对页面进行惰性评估,不应该将其加载到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++;
    }
}

如果要对页面进行惰性评估,则不应将其加载到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++;
    }
}
您可以创建自己的IEnumerable类型,并提供自己的Skip实现:

您可以创建自己的IEnumerable类型,并提供自己的Skip实现:


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

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());
    }
}

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

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());
    }
}

希望有人发现这种方法很有帮助。NET中的泛型惰性类适用于这种情况

//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();

希望有人发现这种方法很有帮助。NET中的泛型惰性类适用于这种情况

//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();


我可能完全误解了你的要求。。但是如果您的代码花费的时间太长。。也许您会考虑删除线程。Sleep1000;你可能需要某种委托,它能在10秒内启动你的代码。你…吗?你能澄清一下这个问题吗?我得等一下。。你是说这是你必须遵守的约束,还是说你的代码目前需要10秒才能完成?我无法删除Thread.Sleep。我想删除对前100个不必要元素的回避。这个问题实际上是有意义的,一旦你通过了有点难以解释的初始版本。我对它进行了大量的编辑来解释它的意思。我可能完全误解了你的要求。。但是如果您的代码花费的时间太长。。也许您会考虑删除线程。Sleep1000;你可能需要某种委托,它能在10秒内启动你的代码。你…吗?你能澄清一下这个问题吗?我得等一下。。你是说这是你必须遵守的约束,还是说你的代码目前需要10秒才能完成?我无法删除Thread.Sleep。我想删除对前100个不必要元素的回避。这个问题实际上是有意义的,一旦你通过了有点难以解释的初始版本。我对它进行了大量的编辑,以解释它的含义。我想得到与linq兼容的方法我想得到与linq兼容的方法我会考虑这段代码,但我会批量获得数据。在Thread.Sleep1000之后,我有10个=限制项。lambda没有收益率回报。嗯,如果是这样的话,你的问题就有点棘手了。。您还应该能够返回一个Func,如果不需要,它只会忽略加载/睡眠。如果你这样做,你必须考虑到每批可能不会有10件物品,因此每批物品会有不止一件物品返回。我也考虑过这种方法,但它与请求是成批处理的事实并不完全一致。@romkyns:不,我同意。当您批量加载物品时,此解决方案并不真正适用..:-/我会考虑这段代码,但我会批量获取数据。在Thread.Sleep1000之后,我有10个=限制项。lambda没有收益率回报。嗯,如果是这样的话,你的问题就有点棘手了。。您还应该能够返回一个Func,如果不需要,它只会忽略加载/睡眠。如果你这样做,你必须考虑到每批可能不会有10件物品,因此每批物品会有不止一件物品返回。我也考虑过这种方法,但它与请求是成批处理的事实并不完全一致。@romkyns:不,我同意。当您批量加载物品时,此解决方案并不真正适用..:-/哇,这就是我想要的。不幸的是,如果你把它作为IEnumerable传递给别人调用。跳过它,编译器只会注意到扩展方法,因此它仍然会很慢,除非LINQ Skip方法实际检查类是否实现了Skip,但我对此深表怀疑。@romkyns如果你将该项强制转换为IEnumerable,它确实会使用LINQ Skip扩展,而不是此方法。哇,这就是我要寻找的。不幸的是,如果你将其作为IEnumerable传递,并有人调用。跳过它,c
ompiler只会注意扩展方法,因此它仍然会很慢,除非LINQ Skip方法实际检查类是否实现了Skip,但我非常怀疑。@romkyns如果将项强制转换为IEnumerable,它确实会使用LINQ Skip扩展,而不是此方法。