8

我试图了解 C# 编译器在链接 linq 方法时所做的事情,特别是在多次链接相同方法时。

简单示例:假设我正在尝试根据两个条件过滤一个整数序列。

最明显的事情是这样的:

IEnumerable<int> Method1(IEnumerable<int> input)
{
    return input.Where(i => i % 3 == 0 && i % 5 == 0);
}

但是我们可以链接 where 方法,每个方法都有一个条件:

IEnumerable<int> Method2(IEnumerable<int> input)
{
    return input.Where(i => i % 3 == 0).Where(i => i % 5 == 0);
}

我在 Reflector 中查看了 IL;这两种方法显然不同,但目前进一步分析它超出了我的知识范围:)

我想知道:
a)编译器在每个实例中的不同之处,以及原因。
b)是否有任何性能影响(不是试图微优化;只是好奇!)

4

2 回答 2

11

(a)的答案很简短,但我将在下面详细介绍:

编译器实际上并没有进行链接——它发生在运行时,通过对象的正常组织!这里的魔力远没有乍看起来的那么神奇——Jon Skeet最近完成了他的博客系列中的“Where 子句”步骤,重新实现 LINQ to Objects。我建议通读一遍。

简而言之,发生的情况是这样的:每次调用Where扩展方法时,它都会返回一个新WhereEnumerable对象,该对象具有两件事 - 对前一个的引用IEnumerable(您调用Where的那个),以及您提供的 lambda。

当您开始对此进行迭代时WhereEnumerable(例如,在foreach代码的后面部分),在内部它只是开始迭代IEnumerable它所引用的。

“这foreach只是问我序列中的下一个元素,所以我转身问你序列中的下一个元素

这一直沿链向下直到我们到达原点,这实际上是某种数组或真实元素的存储。当每个 Enumerable 然后说“好的,这是我的元素”时,将它传递回链,它也应用自己的自定义逻辑。对于 a Where,它应用 lambda 来查看元素是否通过条件。如果是这样,它允许它继续到下一个调用者。如果失败,它会在该点停止,返回其引用的 Enumerable,并请求下一个元素。

这种情况一直发生,直到每个人都MoveNext返回 false,这意味着枚举完成并且没有更多元素。

要回答(b)总是有区别的,但在这里它太微不足道了。别担心 :)

于 2010-11-12T02:15:07.367 回答
1
  1. 第一个将使用一个迭代器,第二个将使用两个。也就是说,第一个设置一个阶段的管道,第二个将涉及两个阶段。

  2. 两个迭代器对一个迭代器有轻微的性能劣势。

于 2010-11-12T02:12:18.910 回答