延迟执行的经典例子

我们用 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

延迟执行的经典例子:我们用 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;  距离上一次更新,已经过去了一周,说起来实在是有些惭愧。一方面,近日的工作的重心在于前端api这块,没有时间精力再去补充C#的相关知识,另一方面,感觉学习的劲头确实不如从前了,近日提笔,也算是一种反思惊醒。今日整理的 Linq 知识,也是我一直想整理的,尤其在遇到EFcore进行联合 查询 的时候就遇到了 linq ,于是便有了下文。 本文整理转载于博客园:https://www.cnblogs.com/dotnet261010/p/8278793.html。 知乎:https://zhuanlan.zhih
LINQ 查询 延迟 执行 LINQ 中,另一个很重要的特性就是 延迟 执行 ,也可以说是 延迟 加载。它是指 查询 操作并不是在 查询 运算符定义的时候 执行 ,真正使用集合中的数据时才 执行 ,例如遍历数据集合时调用MoveNext方法会触发 查询 操作,下面是一个简单的示例:var numbers = new List<int>(); numbers .Add (1); IEnumerable<int> query = numbe
在使用 LINQ 查询 的过程中存在着两种 查询 方式,一种是 立即 执行 ,另一种是 延迟 执行 。下面将主要讲解 LINQ 的特殊支持—— 延迟 执行 。 2. 延迟 执行 延迟 执行 意味着,他们不是在 查询 创建的时候 执行 ,而是在使用 foreach 语句遍历的时候 执行 (换句话说,当 GetEnumera tor 的 MoveNext 方法被调用时)。现在考虑下面这种 查询 的实现: static void ...