LINQ 的查询执行何时是延迟执行,何时是立即执行,以及查询的复用

ccoderlh 2008-01-15 03:07:09
延迟执行的经典例子:

我们用 select ++i 就可以看到在foreach 时候,查询才被执行。

public static void Linq99()
{
int[] numbers = new int[] { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 };
int i = 0;
var q = from n in numbers select ++i;
foreach (var v in q)
Console.WriteLine("v = {0}, i = {1}", v, i);
}

输出结果:

v = 1, i = 1
v = 2, i = 2
v = 3, i = 3
v = 4, i = 4
v = 5, i = 5
v = 6, i = 6
v = 7, i = 7
v = 8, i = 8
v = 9, i = 9
v = 10, i = 10

foreach每一个遍历的时候,select出来的值和当前i的值都是一样的。



立即执行的经典例子:

public static void Linq99()
{
int[] numbers = new int[] { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 };
int i = 0;
var q = (from n in numbers select ++i).ToList();
foreach (var v in q)
Console.WriteLine("v = {0}, i = {1}", v, i);
}

执行结果:

v = 1, i = 10
v = 2, i = 10
v = 3, i = 10
v = 4, i = 10
v = 5, i = 10
v = 6, i = 10
v = 7, i = 10
v = 8, i = 10
v = 9, i = 10
v = 10, i = 10

这个例子的代码跟上面延迟执行的例子代码唯一的差别在于多了一个.ToList();
这也可以证明我们之前提到的原则:

只有到用的时候才会去执行查询

由于 .ToList(); 的存在,在这里就要用到了,所以在这里就执行了查询,而不是在foreach中执行查询。注意,这时候出来的结果是一个数组了.参看后面的几个例子.


执行的一个特殊情况:重复执行

请看下面例子:

查询出一个int数组中小于3的数字。

下面例子中在第一次查询后,对数据源作了修改,然后再作第二次查询,我们可以看到第二次我们不需要再作

lowNumbers = from n in numbers where n <= 3 select n; 这样的定义,而是直接使用 foreach (int n in lowNumbers)。另外这两次的返回结果是不同的,因为我们
在第一次查询后,对数据源作了修改。

public static void Linq101()
{
int[] numbers = new int[] { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 };
var lowNumbers = from n in numbers where n <= 3 select n;
Console.WriteLine("First run numbers <= 3:");
foreach (int n in lowNumbers)
Console.WriteLine(n);

for (int i = 0; i < 10; i++)
numbers[i] = -numbers[i];

Console.WriteLine("Second run numbers <= 3:");
foreach (int n in lowNumbers)
Console.WriteLine(n);
}

输出结果:

First run numbers <= 3:
1
3
2
0
Second run numbers <= 3:
-5
-4
-1
-3
-9
-8
-6
-7
-2
0

以上三个例子均来自 101 LINQ Samples



下面我们再来看几个例子,加深对查询执行的理解:

重复查询的再一个例子:

public static void Linq102()
{
int[] numbers = new int[] { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 };
int i = 0;
var q = from n in numbers select ++i;
foreach (var v in q)
Console.WriteLine("v = {0}, i = {1}", v, i);
foreach (var v in q)
Console.WriteLine("v = {0}, i = {1}", v, i);
}

执行结果:

v = 1, i = 1
v = 2, i = 2
v = 3, i = 3
v = 4, i = 4
v = 5, i = 5
v = 6, i = 6
v = 7, i = 7
v = 8, i = 8
v = 9, i = 9
v = 10, i = 10
v = 11, i = 11
v = 12, i = 12
v = 13, i = 13
v = 14, i = 14
v = 15, i = 15
v = 16, i = 16
v = 17, i = 17
v = 18, i = 18
v = 19, i = 19
v = 20, i = 20



只执行一次的立即查询:

public static void Linq102()
{
int[] numbers = new int[] { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 };
int i = 0;
var q = (from n in numbers select ++i).ToList();
foreach (var v in q)
Console.WriteLine("v = {0}, i = {1}", v, i);
foreach (var v in q)
Console.WriteLine("v = {0}, i = {1}", v, i);
}

执行结果:

v = 1, i = 10
v = 2, i = 10
v = 3, i = 10
v = 4, i = 10
v = 5, i = 10
v = 6, i = 10
v = 7, i = 10
v = 8, i = 10
v = 9, i = 10
v = 10, i = 10
v = 1, i = 10
v = 2, i = 10
v = 3, i = 10
v = 4, i = 10
v = 5, i = 10
v = 6, i = 10
v = 7, i = 10
v = 8, i = 10
v = 9, i = 10
v = 10, i = 10

那些函数会导致立即执行查询:

以下几个扩展函数会导致LINQ会立即执行。并且只执行一次。

.ToArray();

.ToList();

.ToDictionary(k => k);

比如:

public static void Linq102()
{
int[] numbers = new int[] { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 };
int i = 0;
var q = (from n in numbers select ++i).ToDictionary(k => k);
foreach (var v in q)
Console.WriteLine("v = {0}, i = {1}", v, i);
foreach (var v in q)
Console.WriteLine("v = {0}, i = {1}", v, i);
}

输出结果就是:

v = [1, 1], i = 10
v = [2, 2], i = 10
v = [3, 3], i = 10
v = [4, 4], i = 10
v = [5, 5], i = 10
v = [6, 6], i = 10
v = [7, 7], i = 10
v = [8, 8], i = 10
v = [9, 9], i = 10
v = [10, 10], i = 10
v = [1, 1], i = 10
v = [2, 2], i = 10
v = [3, 3], i = 10
v = [4, 4], i = 10
v = [5, 5], i = 10
v = [6, 6], i = 10
v = [7, 7], i = 10
v = [8, 8], i = 10
v = [9, 9], i = 10
v = [10, 10], i = 10



小结:

Q:通过上面几个例子,我们该如何理解LINQ的查询何时执行呢?

A:LINQ的查询执行遵循以下原则:

1、一般情况下(除了下面第三条说的情况),LINQ都是延迟执行,原因:以DLINQ为例,越晚被执行,对业务逻辑的理解就越清晰,DLINQ查询对数据库的请求压力越小。编译器对LINQ查询优化可作的事情越多。

2、由于是延迟执行,也就是调用的时候才去执行。这样调用一次就被执行一次,这样就具备了重复执行的功能,参看之前的几个重复执行的例子。而这个重复执行是不需要再此书写一边查询语句的。

3、如果查询中我们对查询结果使用了 ToArray、ToList、ToDictionary 这些转换成集合的扩展方法。使用这时候出来的对象是一个独立的集合数组,而不是LINQ查询,所以这时候不会出现多次查询,而只是一次查询。

即:var q = from n in numbers select ++i ; 这样一条语句我们可以认为它记录的不是等号右边的结果,而是记录的等号右边的表达式。

而 var q = (from n in numbers select ++i).ToDictionary(k => k); 这样一条语句我们记录的是等号右边的计算结果,而不是表达式。


为理解上面说明,我们可以再看两个例子:

public static void Linq102()
{
int[] numbers = new int[] { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 };
int i = 0;
var q = from n in numbers select ++i;
var qq = q.ToDictionary(k => k);
foreach (var v in q)
Console.WriteLine("v = {0}, i = {1}", v, i);
foreach (var v in q)
Console.WriteLine("v = {0}, i = {1}", v, i);
}

输出结果:

v = 11, i = 11
v = 12, i = 12
v = 13, i = 13
v = 14, i = 14
v = 15, i = 15
v = 16, i = 16
v = 17, i = 17
v = 18, i = 18
v = 19, i = 19
v = 20, i = 20
v = 21, i = 21
v = 22, i = 22
v = 23, i = 23
v = 24, i = 24
v = 25, i = 25
v = 26, i = 26
v = 27, i = 27
v = 28, i = 28
v = 29, i = 29
v = 30, i = 30



public static void Linq102()
{
int[] numbers = new int[] { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 };
int i = 0;
var q = from n in numbers select ++i;
var qq = q.ToDictionary(k => k);
foreach (var v in qq)
Console.WriteLine("v = {0}, i = {1}", v, i);
foreach (var v in qq)
Console.WriteLine("v = {0}, i = {1}", v, i);
}

输出结果为:

v = [1, 1], i = 10
v = [2, 2], i = 10
v = [3, 3], i = 10
v = [4, 4], i = 10
v = [5, 5], i = 10
v = [6, 6], i = 10
v = [7, 7], i = 10
v = [8, 8], i = 10
v = [9, 9], i = 10
v = [10, 10], i = 10
v = [1, 1], i = 10
v = [2, 2], i = 10
v = [3, 3], i = 10
v = [4, 4], i = 10
v = [5, 5], i = 10
v = [6, 6], i = 10
v = [7, 7], i = 10
v = [8, 8], i = 10
v = [9, 9], i = 10
v = [10, 10], i = 10



参考资料:

DLinq Query Execution and Object Identity - LINQ Tutorials

101 LINQ Samples


转自:http://blog.csdn.net/ghj1976/archive/2007/07/03/1676894.aspx
...全文
107 2 打赏 收藏 转发到动态 举报
写回复
用AI写文章
2 条回复
切换为时间正序
请发表友善的回复…
发表回复
SlaughtChen 2008-07-24
  • 打赏
  • 举报
回复
板凳!遗憾,才看到.
lnwuyaowei 2008-01-15
  • 打赏
  • 举报
回复
非常不错.

8,497

社区成员

发帖
与我相关
我的任务
社区描述
.NET技术 LINQ
社区管理员
  • LINQ
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧