简单的例子:假设我试图根据两个条件过滤一系列int.
最明显的事情是这样的:
IEnumerable<int> Method1(IEnumerable<int> input) { return input.Where(i => i % 3 == 0 && i % 5 == 0); }
IEnumerable<int> Method2(IEnumerable<int> input) { return input.Where(i => i % 3 == 0).Where(i => i % 5 == 0); }
我看了反射器中的IL;这两种方法明显不同,但目前我不知道进一步分析它:)
我想找出:
a)编译器在每个实例中做的不同,以及为什么.
b)是否有任何性能影响(不是试图微观优化;只是好奇!)
解决方法
编译器实际上并不进行链接 – 它通过对象的正常组织在运行时发生!这里的魔力远不如乍看之下 – 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),总会有不同之处,但是这里太麻烦了.别担心:)