延迟LINQ查询执行实际上如何工作? [英] How does deferred LINQ query execution actually work?

查看:41
本文介绍了延迟LINQ查询执行实际上如何工作?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

最近我遇到了这样的问题: What numbers will be printed considering the following code:

Recently I faced such question: What numbers will be printed considering the following code:

class Program
{
    static void Main(string[] args)
    {
        int[] numbers = { 1, 3, 5, 7, 9 };
        int threshold = 6;
        var query = from value in numbers where value >= threshold select value;

        threshold = 3;
        var result = query.ToList();

        result.ForEach(Console.WriteLine);
        Console.ReadLine();
    }
}

答案:3, 5, 7, 9

这让我很惊讶.我以为threshold值将在查询构造时放到堆栈中,然后在执行时再放回去,并在没有发生这种情况的条件下使用该值.

Which was quite surprising to me. I thought that threshold value will be put onto stack at the query construction and later at execution time, that number will be pulled back and used in the condition..which didn't happen.

另一种情况(即将执行前将numbers设置为null):

Another case (numbers is set to null just before execution):

    static void Main(string[] args)
    {
        int[] numbers = { 1, 3, 5, 7, 9 };
        int threshold = 6;
        var query = from value in numbers where value >= threshold select value;

        threshold = 3;
        numbers = null;
        var result = query.ToList();
        ...
    }

似乎对查询没有影响.它打印出与上一个示例完全相同的答案.

Seems to have no effect on the query. It prints out exactly the same answer as in previous example.

有人可以帮助我了解幕后的真实情况吗?为什么更改threshold不会对查询执行产生影响,而更改numbers却不会呢?

Could anyone help me understand what is really going on behind the scene? Why changing threshold has the impact on the query execution while changing numbers doesn't?

推荐答案

您的查询可以使用以下方法语法编写:

Your query can be written like this in method syntax:

var query = numbers.Where(value => value >= threshold);

或者:

Func<int, bool> predicate = delegate(value) {
    return value >= threshold;
}
IEnumerable<int> query = numbers.Where(predicate);

这些代码段(包括您自己的查询语法查询)都是等效的.

These pieces of code (including your own query in query syntax) are all equivalent.

当您像这样展开查询时,您会看到predicate关闭.这意味着它将在执行时采用该值.编译器将生成一个实际的(非匿名)方法来解决这个问题.该方法在声明时将不会执行,而是在枚举query时对每个项目执行(执行是 deferred ).由于枚举发生在更改threshold的值之后(并且threshold是闭包),因此将使用新值.

When you unroll the query like that, you see that predicate is an anonymous method and threshold is a closure in that method. That means it will assume the value at the time of execution. The compiler will generate an actual (non-anonymous) method that will take care of that. The method will not be executed when it's declared, but for each item when query is enumerated (the execution is deferred). Since the enumeration happens after the value of threshold is changed (and threshold is a closure), the new value is used.

numbers设置为null时,将引用设置为无处,但该对象仍然存在.由Where返回的IEnumerable(并在query中引用)仍然引用它,并且现在的初始引用是null都没关系.

When you set numbers to null, you set the reference to nowhere, but the object still exists. The IEnumerable returned by Where (and referenced in query) still references it and it does not matter that the initial reference is null now.

这说明了行为:numbersthreshold在延迟执行中扮演不同的角色. numbers是对枚举数组的引用,而threshold是局部变量,其范围被转发"到匿名方法.

That explains the behavior: numbers and threshold play different roles in the deferred execution. numbers is a reference to the array that is enumerated, while threshold is a local variable, whose scope is "forwarded" to the anonymous method.

扩展,第1部分:枚举过程中闭包的修改

当替换行时,您可以使示例更进一步...

You can take your example one step further when you replace the line...

var result = query.ToList();

...具有:

List<int> result = new List<int>();
foreach(int value in query) {
    threshold = 8;
    result.Add(value);
}

您正在做的是在数组迭代期间更改threshold 的值.第一次单击循环的主体时(value为3时),将阈值更改为8,这意味着将跳过值5和7,下一个要添加到列表中的值是9.原因是threshold的值将在每次迭代时再次评估,并且将使用 then 有效值.而且由于阈值已更改为8,因此数字5和7不再等于或等于.

What you are doing is to change the value of threshold during the iteration of your array. When you hit the body of the loop the first time (when value is 3), you change the threshold to 8, which means the values 5 and 7 will be skipped and the next value to be added to the list is 9. The reason is that the value of threshold will be evaluated again on each iteration and the then valid value will be used. And since the threshold has changed to 8, the numbers 5 and 7 do not evaluate as greater or equal anymore.

扩展,第2部分:实体框架不同

为了使事情变得更复杂,当您使用LINQ提供程序创建与原始查询不同的查询然后执行它时,情况会稍有不同.最常见的示例是实体框架(EF)和LINQ2SQL(现已被EF大大取代).这些提供程序在枚举之前从原始查询创建一个SQL查询.由于这一次闭包的值仅计算一次(实际上不是闭包,因为编译器会生成表达式树而不是匿名方法),因此在枚举期间threshold中的更改不会对闭包的值产生影响.结果.这些更改将在查询提交到数据库之后发生.

To make things more complicated, when you use LINQ providers that create a different query from your original and then execute it, things are slightly different. The most common examples are Entity Framework (EF) and LINQ2SQL (now largely superseded by EF). These providers create an SQL query from the original query before the enumeration. Since this time the value of the closure is evaluated only once (it actually is not a closure, because the compiler generates an expression tree and not an anonymous method), changes in threshold during the enumeration have no effect on the result. These changes happen after the query is submitted to the database.

从中得到的教训是,您必须始终了解所使用的LINQ风格,并且对它的内部工作有所了解是一个优势.

The lesson from this is that you have to be always aware which flavor of LINQ you are using and that some understanding of its inner workings is an advantage.

这篇关于延迟LINQ查询执行实际上如何工作?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆