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

了解C#编译器如何处理链接linq方法,c#,linq,cil,method-chaining,C#,Linq,Cil,Method Chaining,当我链接linq方法时,特别是在多次链接同一个方法时,我正试图了解C#编译器所做的事情 简单示例:假设我正在尝试根据两个条件过滤一系列整数 最明显的做法是: IEnumerable<int> Method1(IEnumerable<int> input) { return input.Where(i => i % 3 == 0 && i % 5 == 0); } IEnumerable方法1(IEnumerable输入) { 返回输入。其中

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

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

最明显的做法是:

IEnumerable<int> Method1(IEnumerable<int> input)
{
    return input.Where(i => i % 3 == 0 && i % 5 == 0);
}
IEnumerable方法1(IEnumerable输入)
{
返回输入。其中(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);
}
IEnumerable方法2(IEnumerable输入)
{
返回输入。其中(i=>i%3==0)。其中(i=>i%5==0);
}
我看了一下反射器中的IL;这两种方法显然不同,但进一步分析目前我还不知道:)

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

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

  • 两个迭代器比一个迭代器有轻微的性能劣势

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

    编译器实际上并没有进行链接——它发生在运行时,通过对象的正常组织!与乍一看可能出现的情况相比,这里的魔力要小得多——Jon Skeet在他的博客系列中,重新实现LINQ到对象。我建议你通读一遍

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

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

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

    这一直延续到我们到达原点,它实际上是某种数组或真实元素的存储。当每个可枚举项都说“好的,这是我的元素”并将其传回链时,它也应用自己的自定义逻辑。对于
    中的
    ,它应用lambda来查看元素是否通过标准。如果是这样,它允许它继续到下一个调用者。如果失败,它将在该点停止,返回其引用的可枚举项,并请求下一个元素

    这会一直发生,直到每个人的
    MoveNext
    返回false,这意味着枚举已完成,没有更多元素


    要回答(b),总是有区别的,但在这里,它太琐碎了,不值得麻烦。别担心:)

    回答得好。我们需要更多像Stackoverflow这样的材料。