开发者社区> 吞吞吐吐的> 正文
阿里云
为了无法计算的价值
打开APP
阿里云APP内打开

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的查询何时执行呢?

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

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

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

3、如果查询中我们对查询结果使用了 ToArrayToListToDictionary 这些转换成集合的扩展方法。使用这时候出来的对象是一个独立的集合数组,而不是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

发表于 @ 16:21 | (阅读:4258;评论:2)

LINQ的经典例子-WhereSelectSelectManySkipWhile子句中使用数组索引

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扩展方法的例子:

我们有一个字符串数组,一次是09的英文单词,我们查询出这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);

这行代码都赶了些什么?

 

1Where子句其实是用扩展方法来实现的

如果你对扩展方法不熟悉,请先看我之前的几篇博客:

C#3.0 中的扩展方法 (Extension Methods)

C#3.0 中使用扩展方法来扩展接口

Orcas Beta1 对多个同名扩展方法的处理逻辑

微软替我们实现的 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 代码。

 

2Where 子句参数书写的是Lambda 表达式

如果你不清楚什么是Lambda 表达式,你可以参看我之前的博客:

C# 3.0 Lambda表达式(Lambda Expressions

(dd, aa) => dd.Length < aa 就相当于 C# 2.0 的匿名函数。

LINQ中所有关键字比如 SelectSelectMany Count All 等等其实都是用扩展方法来实现的。上面的用法同样也适用于这些关键字子句。

 

3、这个Where子句中Lambda 表达式第二个参数是数组索引,我们可以在Lambda 表达式内部使用数组索引。来做一些复杂的判断。

具有数组索引的LINQ关键字除了Where还以下几个SelectSelectMany 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 FirstOrDefaultAnyAllCount 子句

注意:

101 LINQ Samples  First - IndexedFirstOrDefault - Indexed Any - IndexedAll - IndexedCount - 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);
}

 

本文转自温景良博客园博客,原文链接:http://www.cnblogs.com/wenjl520/archive/2008/10/28/1321730.html,如需转载请自行联系原作者

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

相关文章
Linux:磁盘情况查询+磁盘情况的工作使用指令(内含使用实例)
Linux:磁盘情况查询+磁盘情况的工作使用指令(内含使用实例)
5 0
JS查漏补缺——执行上下文、作用域
JS查漏补缺系列是我在学习JS高级语法时做的笔记,通过实践费曼学习法进一步加深自己对其的理解,也希望别人能通过我的笔记能学习到相关的知识点。这一次我们来了解执行上下文和作用域
26 0
JAVA:私有成员变量的调用 与 构造函数私有化 、构造函数保持成员变量私有性
私有成员变量的调用 与 构造函数私有化 、构造函数保持成员变量私有性的介绍
89 0
任务分解与函数拆分以及面向未来编程的思想分享
任务分解与函数拆分以及面向未来编程的思想分享
71 0
阿里云 IoT Studio 服务开发云市场API调用示例
本文主要演示如何通过云市场API节点调用印刷文字识别-身份证识别/OCR文字识别接口。
345 0
阿里云 IoT Studio 服务开发云市场API调用示例
使用云市场API节点,可调用您在阿里云云市场购买的API,如天气预报,身份证识别等,并可以根据API的返回数据开发业务逻辑。本文主要演示如何通过云市场API节点调用印刷文字识别-身份证识别/OCR文字识别接口。
2460 0
微信小程序、支付宝小程序、百度小程序 阿里云推出多端小程序
阿里云于近日推出多端小程序平台(包括微信小程序、支付宝小程序、百度小程序、今日头条小程序),有上百款的模板,自主选择,可以直接购买套用即可。
2706 0
阿里云支付:可以更换绑定的支付宝账号吗?
阿里云支付:可以更换绑定的支付宝账号吗?支付宝账号与阿里云账号绑定后,不能随意更改。如果由于人员离职、业务变更等特殊场景需要变更绑定的支付宝账号,需先解绑原支付宝账号。请登录进入 工单入口 提交工单,由客服人工审核后解绑。
5771 0
阿里云支付:可以更换绑定的支付宝账号吗?
支付宝账号与阿里云账号绑定后,不能随意更改。如果由于人员离职、业务变更等特殊场景需要变更绑定的支付宝账号,需先解绑原支付宝账号。请登录进入 工单入口 提交工单,由客服人工审核后解绑。提交工单时,需附资料:企业:需提供最新颁发的营业执照副本原件的彩色扫描件或者照片。
2401 0
文章
问答
文章排行榜
最热
最新
相关电子书
更多
fibjs 模块重构从回调到协程--陈垒
立即下载
低代码开发师(初级)实战教程
立即下载
阿里巴巴DevOps 最佳实践手册
立即下载