LINQ方法执行顺序

4
我正在阅读一本有关高级C#的书籍。此时,我正在阅读以下内容:
采用委托语法的Linq查询方法的幕后操作。
迄今为止,我已经了解了Where、Select、Skip、SkipWhile、Take、TakeWhile等方法。并且我还知道Lazy和Eager执行以及由其中一些方法返回的迭代器。
延迟执行是执行模型的一种模式,CLR确保只有在需要从IEnumerable-based信息源中提取值时才会提取该值。当任何Linq运算符使用延迟执行时,CLR将相关信息(如原始序列、谓词或选择器(如果有))封装到一个迭代器中,该迭代器将在使用ToList方法或ForEach方法或手动使用C#中的底层GetEnumerator和MoveNext方法从原始序列中提取信息时使用。
现在让我们看看以下两个例子:
IList<int> series = new List<int>() { 1, 2, 3, 4, 5, 6, 7 };

// First example
series.Where(x => x > 0).TakeWhile(x => x > 0).ToList();

// Second example
series.Where(x => x > 0).Take(4).ToList();

当我设置断点并调试这两个语句时,我可以看到一个区别。 TakeWhile() 方法在满足条件的情况下执行 Where 语句。但是,Take 方法不是这种情况。
第一条语句: enter image description here enter image description here
第二条语句: enter image description here enter image description here
你能解释一下为什么吗?
2个回答

5

虽然不是很清楚您的意思,但如果您想知道为什么在 TakeWhile 的 lambda 表达式中触发了断点,而在 Take 中没有触发,则是因为 Take 根本不接受委托 - 它只接受数字。在查找要返回的值时,没有用户定义的代码需要执行,因此没有断点可触发。

在您的 TakeWhile 示例中,您有两个 lambda 表达式 - 一个用于 Where,另一个用于 TakeWhile。因此,您可以打断到其中任何一个 lambda 表达式。

重要的是要理解,WhereTakeWhile 方法本身只会被调用一次 - 但它们返回的序列会对它们遇到的每个值求值。

您可能想查看我的Edulinq 博客系列,以获取有关 LINQ 内部工作原理的更多详细信息。


@FarhadJabiyev:你在LINQ签名中看到过MulticastDelegate吗?我不记得曾经看到过这个。我不确定你所说的“CLR将语句中的所有方法添加到此委托”是什么意思。听起来你应该用示例代码更详细地提出一个新问题。 - Jon Skeet
好的,让我详细解释一下我的问题。我们来看一下我问题中的第一个例子。 我在那里使用了两个匿名函数。一个用于 Where 方法,另一个用于 TakeWhile 方法。据我所知,C# 编译器会在编译时使用匿名方法构造一个新的方法。CLR 将使用此方法实例化一个 MulticastDelegate 实例,并将其作为谓词传递给方法。对于序列中的每个项,CLR 只需执行由此委托引用的所有方法即可。 我说得对吗? - Farhad Jabiyev
@FarhadJabiyev:不,编译器会创建一个新的Func<string, bool>实例或其他类型。MulticastDelegate是一个抽象类。你所说的“所有方法”并不清楚 - 这里只有一个方法。但是,你的问题与LINQ无关,如果这些细节还不够,请像我之前提到的那样提出一个新问题。 - Jon Skeet
抱歉再次打扰。但是,我已经阅读了您的博客和我再次阅读的书籍。现在,我没有任何问题。但是,我想听听您对一件事的想法。在我的书中,作者一直在说“MulticastDelegate”,而不是“Func<...>”,他说: “The System.Func<TSource,TResult>是从MulticastDelegate派生的。因此,CLR使用方法<Main>b__1创建MulticastDelegate实例。” 那么,这个句子关于创建MulticastDelegate实例的说法是否正确? - Farhad Jabiyev
@FarhadJabiyev:这取决于您所说的“多播委托实例”的确切含义。由于继承关系,Func<Foo,Bar>的实例MulticastDelegate的实例,但是在创建实例时我不会使用该术语-我会谈论正在创建的具体实际类型。(例如,我不会说“CLR创建了System.Object实例”-出于同样的原因,这也是正确的。) - Jon Skeet
显示剩余3条评论

1

好的,TakeWhile 中的条件需要对每个项目进行评估,就像 Where 一样,因此它将为每个项目调用它们中的每一个。

Take(4) 不需要对每个项目进行评估,只需要对 Where 进行评估,因此在第二个例子中,每次只会评估 Where 条件(可能四次)。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接