了解C#编译器如何处理链接linq方法

时间:2010-11-12 01:54:39

标签: c# linq cil method-chaining

我正在尝试围绕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)是否有任何性能影响(不尝试微优化;只是好奇!)

2 个答案:

答案 0 :(得分:8)

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

编译器实际上并不进行链接 - 它通过对象的正常组织在运行时发生!这里的魔法远不如乍看之下 - Jon Skeet recently completed the "Where clause" step在他的博客系列中重新实现LINQ to Objects。我建议你仔细阅读。

在很短的时间内,会发生这样的情况:每次调用Where扩展方法时,它都会返回一个新的WhereEnumerable对象,它有两个东西 - 对前一个{{1}的引用(你打电话给IEnumerable的那个)和你提供的lambda。

当你开始迭代这个Where时(例如,在代码中的WhereEnumerable中),在内部它只是开始迭代它的foreach < / em>已引用。

  

“这个IEnumerable刚刚问了我序列中的下一个元素,所以我转过身来,并要求你提供 序列中的下一个元素。”

直到我们击中原点,这一直是链条,这实际上是某种阵列或真实元素的存储。当每个Enumerable然后说“OK,这是我的元素”将它传递回链时,它也应用它自己的自定义逻辑。对于foreach,它应用lambda来查看元素是否通过了条件。如果是这样,它允许它继续到下一个调用者。如果失败,它将在那一刻停止,转回其引用的Enumerable,并询问下一个元素。

这种情况一直持续到每个人的Where都返回false,这意味着枚举已经完成且没有其他元素。

要回答(b)始终存在差异,但这里的麻烦太过于微不足道了。别担心:)

答案 1 :(得分:1)

  1. 第一个将使用一个迭代器,第二个将使用两个。也就是说,第一个设置一个阶段的管道,第二阶段将涉及两个阶段。

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