C# · 12月 23, 2021

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

我正在尝试围绕C#编译器在链接 linq方法时所做的事情,特别是在多次链接同一方法时.

简单的例子:假设我试图根据两个条件过滤一系列int.

最明显的事情是这样的:

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

我看了反射器中的IL;这两种方法明显不同,但目前我不知道进一步分析它:)

我想找出:
a)编译器在每个实例中做的不同,以及为什么.
b)是否有任何性能影响(不是试图微观优化;只是好奇!)

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

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

在很短的时间内,会发生这样的情况:每次调用Where扩展方法时,它都会返回一个新的WhereEnumerable对象,它有两个东西 – 对前一个IEnumerable的引用(你调用的那个),以及你提供的lambda .

当你开始迭代这个WhereEnumerable时(例如,在你的代码中的foreach中),在内部它只是开始迭代它引用的IEnumerable.

“This foreach just asked me for the next element in my sequence,so I’m turning around and asking you for the next element in your sequence”.

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

这种情况一直持续到每个人的MoveNext都返回false,这意味着枚举已经完成,并且没有更多的元素.

要回答(b),总会有不同之处,但是这里太麻烦了.别担心:)