LINQ 的查询执行何时是延迟执行,何时是立即执行,以及查询的复用
LINQ 的查询执行何时是延迟执行,何时是立即执行,以及查询的复用
延迟执行的经典例子:
我们用 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
下面我们再来看几个例子,加深对查询执行的理解:
重复查询的再一个例子:
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
发表于 @ 16:21 | (阅读:4258;评论:2)
LINQ的经典例子-Where,Select、SelectMany、SkipWhile子句中使用数组索引
Where 子句的用法
我们除了可以如下方式书写带Where子句的LINQ外:
from p in products where p.UnitsInStock > 0 && p.UnitPrice > 3.00M select p;
还可以对数组(所有实现了IEnumerable接口的对象都可以)的实体使用 Where 扩展方法。
把一个查询语句写成多个扩展函数的方式,这其实是编译器处理查询语句的方法,比如下面的查询语句:
int[] arr = new int[] { 8, 5, 89, 3, 56, 4, 1, 58 };
var m = from n in arr where n < 5 orderby n select n;
编译器在编译后,替我们产生的代码等价于如下的代码:
IOrderedSequence<int> m = arr.Where<int>(delegate (int n) {
return (n < 5);
}).OrderBy<int, int>(delegate (int n) {
return n;
});
下面我们来看一个使用Where扩展方法的例子:
我们有一个字符串数组,一次是0到9的英文单词,我们查询出这10个字符的长度比它所在数组的位置 这两个数字比较小的英文单词.
这个查询可能有些绕口,你可以先看下面这些代码:
public static void LinqDemo01()
{
string[] digits = { "zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine" };
var shortDigits = digits.Where((dd, aa) => dd.Length < aa);
Console.WriteLine("Short digits:");
foreach (var d in shortDigits)
Console.WriteLine("The word {0} is shorter than its value.", d);
}
输出结果:
Short digits:
The word five is shorter than its value.
The word six is shorter than its value.
The word seven is shorter than its value.
The word eight is shorter than its value.
The word nine is shorter than its value.
下面我们就来分析上述代码中最核心的代码:
digits.Where((dd, aa) => dd.Length < aa);
这行代码都赶了些什么?
1、Where子句其实是用扩展方法来实现的
如果你对扩展方法不熟悉,请先看我之前的几篇博客:
C#3.0 中的扩展方法 (Extension Methods)
微软替我们实现的 Where 子句对应的扩展函数实际是如下的定义:
namespace System.Linq
{
public delegate TResult Func<TArg0, TArg1, TResult>(TArg0 arg0, TArg1 arg1);
public static class Enumerable
{
public static IEnumerable<TSource> Where<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate);
public static IEnumerable<TSource> Where<TSource>(this IEnumerable<TSource> source, Func<TSource, int, bool> predicate);
}
}
其中红色字体的那个扩展函数,就是我们上面代码实际使用的扩展函数。
我们这个扩展函数参数:Func<TSource, int, bool> predicate 的定义看上面代码的绿色delegate 代码。
2、Where 子句参数书写的是Lambda 表达式
如果你不清楚什么是Lambda 表达式,你可以参看我之前的博客:
C# 3.0 的Lambda表达式(Lambda Expressions)
(dd, aa) => dd.Length < aa 就相当于 C# 2.0 的匿名函数。
LINQ中所有关键字比如 Select,SelectMany, Count, All 等等其实都是用扩展方法来实现的。上面的用法同样也适用于这些关键字子句。
3、这个Where子句中Lambda 表达式第二个参数是数组索引,我们可以在Lambda 表达式内部使用数组索引。来做一些复杂的判断。
具有数组索引的LINQ关键字除了Where还以下几个Select,SelectMany, Count, All
我们下面就来依次举例
Select 子句使用数组索引的例子
下面代码有一个整数数组,我们找出这个数字是否跟他在这个数组的位置一样
public static void LinqDemo01()
{
int[] numbers = { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 };
var numsInPlace = numbers.Select((num, index) => new { Num = num, InPlace = (num == index) });
Console.WriteLine("Number: In-place?");
foreach (var n in numsInPlace)
Console.WriteLine("{0}: {1}", n.Num, n.InPlace);
}
输出结果:
Number: In-place?
5: False
4: False
1: False
3: True
9: False
8: False
6: True
7: True
2: False
0: False
其中我们用到的这个Select子句对应的扩展函数定义,以及其中Func<TSource, int, TResult>委托定义如下:
public static IEnumerable<TResult> Select<TSource, TResult>(this IEnumerable<TSource> source, Func<TSource, int, TResult> selector);
public delegate TResult Func<TArg0, TArg1, TResult>(TArg0 arg0, TArg1 arg1);
SelectMany 子句使用数组索引的例子
几个句子组成的数组,我们希望把这几个句子拆分成单词,并显示每个单词在那个句子中。查询语句如下:
public static void Demo01()
{
string[] text = { "Albert was here",
"Burke slept late",
"Connor is happy" };
var tt = text.SelectMany((s, index) => from ss in s.Split(' ') select new { Word = ss, Index = index });
foreach (var n in tt)
Console.WriteLine("{0}:{1}", n.Word,n.Index);
}
结果:
Albert:0
was:0
here:0
Burke:1
slept:1
late:1
Connor:2
is:2
happy:2
SkipWhile 子句使用数组索引的例子
SkipWhile 意思是一直跳过数据,一直到满足表达式的项时,才开始返回数据,而不管之后的项是否仍然满足表达式,需要注意他跟Where是不一样的,Where是满足条件的记录才返回,SkipWhile 是找到一个满足条件的,然后后面的数据全部返回。
下面例子返回一个整数数组中,这个整数比他自身在这个数组的位置大于等于的第一个位置以及之后的数据。
public static void Linq27()
{
int[] numbers = { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 };
var laterNumbers = numbers.SkipWhile((n, index) => n >= index);
Console.WriteLine("All elements starting from first element less than its position:");
foreach (var n in laterNumbers)
Console.WriteLine(n);
}
输出结果:
All elements starting from first element less than its position:
1
3
9
8
6
7
2
0
First 、FirstOrDefault、Any、All、Count 子句
注意:
101 LINQ Samples 中 First - Indexed、FirstOrDefault - Indexed、 Any - Indexed、All - Indexed、Count - Indexed 这五个例子在 Orcas Beta1中已经不在可用,即下面代码是错误的。
public void Linq60() {
int[] numbers = { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 };
int evenNum = numbers.First((num, index) => (num % 2 == 0) && (index % 2 == 0));
Console.WriteLine("{0} is an even number at an even position within the list.", evenNum);
}
public void Linq63() {
double?[] doubles = { 1.7, 2.3, 4.1, 1.9, 2.9 };
double? num = doubles.FirstOrDefault((n, index) => (n >= index - 0.5 && n <= index + 0.5));
if (num != null)
Console.WriteLine("The value {1} is within 0.5 of its index position.", num);
else
Console.WriteLine("There is no number within 0.5 of its index position.", num);
}
public void Linq68() {
int[] numbers = { -9, -4, -8, -3, -5, -2, -1, -6, -7 };
bool negativeMatch = numbers.Any((n, index) => n == -index);
Console.WriteLine("There is a number that is the negative of its index: {0}", negativeMatch);
}
public void Linq71() {
int[] lowNumbers = { 1, 11, 3, 19, 41, 65, 19 };
int[] highNumbers = { 7, 19, 42, 22, 45, 79, 24 };
bool allLower = lowNumbers.All((num, index) => num < highNumbers[index]);
Console.WriteLine("Each number in the first list is lower than its counterpart in the second list: {0}", allLower);
}
public void Linq75() {
int[] numbers = { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 };
int oddEvenMatches = numbers.Count((n, index) => n % 2 == index % 2);
Console.WriteLine("There are {0} numbers in the list whose odd/even status " +
"matches that of their position.", oddEvenMatches);
}
要实现这个功能,可以用Where 子句,如下:
public static void Linq60()
{
int[] numbers = { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 };
int evenNum = numbers.Where((num,index) =>( num % 2 == 0 && index %2 == 0) ).First();
Console.WriteLine("{0} is an even number at an even position within the list.", evenNum);
}
public static void Linq63()
{
double?[] doubles = { 1.7, 2.3, 4.1, 1.9, 2.9 };
double? num = doubles.Where((n, index) => (n >= index - 0.5 && n <= index + 0.5)).FirstOrDefault();
if (num != null)
Console.WriteLine("The value {1} is within 0.5 of its index position.", num);
else
Console.WriteLine("There is no number within 0.5 of its index position.", num);
}
public static void Linq68()
{
int[] numbers = { -9, -4, -8, -3, -5, -2, -1, -6, -7 };
bool negativeMatch = numbers.Where((n, index) => n == -index).Any();
Console.WriteLine("There is a number that is the negative of its index: {0}", negativeMatch);
}
public static void Linq71()
{
int[] lowNumbers = { 1, 11, 3, 19, 41, 65, 19 };
int[] highNumbers = { 7, 19, 42, 22, 45, 79, 24 };
bool allLower = lowNumbers.Where((num, index) => num < highNumbers[index]).All(n => true);
Console.WriteLine("Each number in the first list is lower than its counterpart in the second list: {0}", allLower);
}
public static void Linq75()
{
int[] numbers = { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 };
int oddEvenMatches = numbers.Where((n, index) => n % 2 == index % 2).Count();
Console.WriteLine("There are {0} numbers in the list whose odd/even status " +
"matches that of their position.", oddEvenMatches);
}