LINQ扩展方法的顺序不影响性能?

Tim*_*ter 20 .net c# linq

我很惊讶,无论我是在前置还是附加LINQ扩展方法都没关系.

经测试Enumerable.FirstOrDefault:

  1. hugeList.Where(x => x.Text.Contains("10000")).FirstOrDefault();
  2. hugeList.FirstOrDefault(x => x.Text.Contains("10000"));

    var hugeList = Enumerable.Range(1, 50000000)
        .Select(i => new { ID = i, Text = "Item" + i });
    
    var sw1 = new System.Diagnostics.Stopwatch();
    var sw2 = new System.Diagnostics.Stopwatch();
    
    sw1.Start();
    for(int i=0;i<1000;i++)
        hugeList.Where(x => x.Text.Contains("10000")).FirstOrDefault();
    sw1.Stop();
    
    sw2.Start();
    for(int i=0;i<1000;i++)
        hugeList.FirstOrDefault(x => x.Text.Contains("10000"));
    sw2.Stop();
    
    var result1 = String.Format("FirstOrDefault after: {0} FirstOrDefault before: {1}", sw1.Elapsed,  sw2.Elapsed);
    //result1: FirstOrDefault after: 00:00:03.3169683 FirstOrDefault before: 00:00:03.0463219
    
    sw2.Restart();
    for (int i = 0; i < 1000; i++)
        hugeList.FirstOrDefault(x => x.Text.Contains("10000"));
    sw2.Stop();
    
    sw1.Restart();
    for (int i = 0; i < 1000; i++)
        hugeList.Where(x => x.Text.Contains("10000")).FirstOrDefault();
    sw1.Stop();
    
    var result2 = String.Format("FirstOrDefault before: {0} FirstOrDefault after: {1}", sw2.Elapsed, sw1.Elapsed);
    //result2: FirstOrDefault before: 00:00:03.6833079 FirstOrDefault after: 00:00:03.1675611
    
    //average after:3.2422647 before: 3.3648149 (all seconds)
    
    Run Code Online (Sandbox Code Playgroud)

我猜想它会更慢,Where因为它必须找到所有匹配的项目,然后取第一个,前面的FirstOrDefault可以产生第一个找到的项目.

问:有人可以解释我为什么走错了路吗?

Eri*_*ert 48

我会猜到前缀会更慢,因为它必须找到所有匹配的项目,然后取第一个和前面的FirstOrDefault可以产生第一个找到的项目.有人可以解释为什么我走错了路吗?

你走错了路,因为你的第一个陈述是错误的.Where不是需要找到所有匹配的物品之前获取第一个匹配项.Where"按需"提取匹配项目; 如果你只要求第一个,它只取出第一个.如果你只要求前两个,它只取得前两个.

Jon Skeet在舞台上表现不错.想象一下,你有三个人.第一个人有一张洗牌的牌.第二个人有一件T恤,上面写着"卡片是红色的".第三个人戳了第二个人说"给我第一张牌".第二个人一遍又一遍地戳第一个人,直到第一个人交出一张红牌,然后第二个人交给第三个人.第二个人没有理由继续戳第一个人; 任务完成了!

现在,如果第二个人的T恤上写着"按等级递增排序",那么我们就会有一个非常不同的情况.现在第二个人确实需要从第一个人那里获得每张卡,以便在将第一张卡交给第三人之前找到套牌中的最低牌.

现在,这应该为您提供必要的直觉,以告知订单何时因性能原因而重要."给我红牌然后对它们进行排序"的最终结果与"对所有卡片进行排序然后给我红色"完全相同,但前者要快得多,因为你不必花时间整理你要丢弃的黑卡.

  • 谢谢你生动的解释.我还有问题要理解延迟执行的_nature_.我不知道自己是否必须实施这样的事情.它是如何实现的,你已经在博客中写过了吗?最后,我怎么知道方法何时直接执行(如`Any`)或延迟执行(如`Where`)?是否所有使用/返回`IEnumerable <T>`执行延迟? (2认同)
  • @TimSchmelter:考虑使用yield实现`IEnumerable`.这些元素不会一次性返回,而是根据要求返回.将"IEnumerable"函数链接在一起相对简单.如果你想更好地了解LINQ的工作原理,我建议你阅读Jon Skeet的优秀[Edulinq](https://msmvps.com/blogs/jon_skeet/archive/tags/Edulinq/default.aspx)系列,在那里他重新实现LINQ.[`Where`](https://msmvps.com/blogs/jon_skeet/archive/2010/09/03/reimplementing-linq-to-objects-part-2-quot-where-quot.aspx)特别相关. (2认同)
  • @TimSchmelter:我不相信"延期执行"的概念首先是足够明确的; 它似乎引起了很多混乱.从某种意义上说**所有**执行都是推迟的; 在调用方法**之前不会执行任何执行**.那么问题是*该方法做了什么*?"IEnumerator"的合同是"当你调用`MoveNext`然后调用`Current`时,我会给你下一个项目.所以你所知道的是,在*之前完成了下一个项目的工作*调用`Current`返回.它可能在*之前很长时间*或*之前*之前完成. (2认同)
  • @TimSchmelter:我同意Brian的观点:Jon的系列节目很有教育意义.您可能还想了解如何实现`yield return`的细节.这个想法非常直截了当; 编译器将代码转换为跟踪*数字*的程序,这实际上意味着"当我最近产生的时候,我的代码行是什么?",并在调用`MoveNext`时对该行进行"转到" .任何类型的延迟执行的关键,无论是迭代器块还是C#5异步块,都要*以某种方式跟踪你下一步需要做什么*,又名什么是我的*延续*? (2认同)

Jam*_*are 11

Where()方法使用延迟执行,并将根据请求提供下一个匹配项.也就是说,Where()不评估并立即返回所有候选对象的序列,它们在迭代时一次提供一个.

由于FirstOrDefault()在第一个项目之后停止,这将导致Where()停止迭代.

可以认为FirstOrDefault()停止执行Where()就像执行了一样break.当然,这并不是那么简单,但实质上,因为FirstOrDefault()一旦找到项目就停止迭代,Where()不需要继续进行.

当然,这是在应用FirstOrDefault()on Where()子句的简单情况下,如果你有其他条款暗示需要考虑所有项目,这可能会产生影响,但在使用Where().FirstOrDefault()' combo or justFirstOrDefault()'时都是如此谓词.

  • 你可能应该在那里提到,在某些操作中,订单**确实是重要的,就像Eric那样.有些情况下没有,例如这个,但说它永远不重要是错误的. (2认同)