LINQ - query 语法

query expression syntax

c#提供了语法编写LINQ查询的快捷方式,称为查询表达式。

query表达式不像是你看起来的那样,不是将SQL嵌入c#的方法。事实上查询表达式的设计主要受到来自LISP和Haskell等函数式编程语言的列表理解的启发,尽管SQL有一定的外观影响。

 

和前一节一样的字符串提取、按长度排序、并转为大写,query expression 语法是这样的:

using System;
using System.Collections.Generic;
using System.Linq;
class LinqDemo
{
    static void Main()
    {
        string[] names = { "Tom", "Dick", "Harry", "Mary", "Jay" };
​
        IEnumerable<string> query =
            from n in names
            where n.Contains ("a") // Filter elements
            orderby n.Length // Sort elements
            select n.ToUpper(); // Translate each element (project)
foreach (string name in query) Console.WriteLine (name);
    }
}

 

查询表达式总是以from子句开头,以select或group子句结尾。

  • 编译器通过将 query expression 翻译成 fluent syntax 来处理它。

 

Range Variables

紧跟着from关键字语法的标识符称为范围变量。

编译器机械的转换到 fluent syntax 是像下面这样的:

names.Where (n => n.Contains ("a")) // Locally scoped n
.OrderBy (n => n.Length) // Locally scoped n
.Select (n => n.ToUpper()) // Locally scoped n

正如您所看到的,n的每个实例的作用域都是自己的lambda表达式。query operator 中的 n 都是局部范围有效。

查询表达式还允许您通过以下子句引入新的范围变量:

• let

• into

• An additional from clause

• join

 

 

Query Syntax 对比 SQL Syntax

表面上看,它们两个非常不同的。

  • LINQ中的子查询只是另一个c#表达式,因此不需要特殊的语法。SQL中的子查询受特殊规则的约束。

  • 使用LINQ,数据逻辑上从左向右流经查询。对于SQL,顺序在数据流方面的结构不是很好,SQL的书写顺序和实际执行顺序有很大不同。

  • LINQ 是和集合的元素顺序有关系的,SQL 对数据集合的顺序不敏感,也可以是无序的。

 

Query Syntax Versus Fluent Syntax

  • Query 和 fluent syntax 各有优势,但是它们的本质是一样的,看实际情况来使用它们.

 

Mixed - Syntax Queries

如果查询操作符不支持查询语法,则可以混合使用查询语法和连贯语法。

怎么方便怎么来,例如:

string[] names = { "Tom", "Dick", "Harry", "Mary", "Jay" };
int matches = (from n in names where n.Contains ("a") select n).Count(); //3
string first = (from n in names orderby n select n).First(); // Dick

 

Deferred Execution 延时执行

一个重要特性是 大多数查询操作符,它们不是在构造时执行而是在枚举时执行

看下面这个 query:

var numbers = new List<int> { 1 };
​
IEnumerable<int> query = numbers.Select (n => n * 10); // Build query
​
numbers.Add (2); // Sneak in an extra element
foreach (int n in query)
    Console.Write (n + "|"); // 10|20|

 

上面的例子说明了Select 操作符的计算延时到 foreach运行时执行。这被称为延时执行。同样的事情也发生在委托上。

Action a = () => Console.WriteLine ("Foo");
​
// We've not written anything to the Console yet. Now let's run it:
​
a(); // Deferred execution!

 

所有标准查询操作符都提供延迟执行,但以下情况除外::

• 操作符返回单个 element or scalar value, 例如 First or Count

• 还有下例 conversion operators:

ToArray, ToList, ToDictionary, ToLookup

 

上述这些操作符会导致 query execution 立马执行, 因为它们的返回类型没有提供延迟执行的机制。

Count 方法,返回一个整数,它不是一个可枚举的类型,所以会立马执行。

 

延迟执行很重要因为使得 查询构造 与 查询执行 解耦。这允许你在几个步骤中构建一个查询,并使数据库查询成为可能。子查询中的所有内容都受制于延迟执行—包括聚合和转换方法。

 

 

Reevaluation 重新求值

延迟执行还有另一个结果,当你重新枚举时,一个延迟执行查询被重新求值:

有几个原因说明重新求值的不好的地方:

  • 想要缓存某个时间点的结果时。

  • 有些查询是计算密集型的(或者依赖于查询远程数据库),所以不想不必要地重复它们。

解决办法是:

定义一个临时变量 缓存它:

var numbers = new List<int>() { 1, 2 };
List<int> timesTen = numbers
.Select (n => n * 10).ToList(); // Executes immediately into a List<int>
​
numbers.Clear();
Console.WriteLine (timesTen.Count); // Still 2

 

Captured Variables

延时查询的另一个问题就是,If your query’s lambda expressions capture outer variables, the query will honor the value of those variables at the time the query runs: (查询将在查询运行时尊重这些变量的值:)

啥叫在查询时尊重这些变量的值呢?看下面的例子就能明白,因为是延时计算,在表达式中的变量只是一个占位符,你写好LINQ 语句后,这个占位符在你不注意时换成其他的,结果就会和你预期的 不一样了。

int factor = 10;
IEnumerable<int> query = numbers.Select (n => n * factor);
factor = 20;
foreach (int n in query) Console.Write (n + "|"); // 20|40| ,本来以为会是10,20.

 

在for循环中构建查询时,这可能是一个陷阱。例如,假设我们想从字符串中删除所有元音。下面虽然效率不高,但给出了正确的结果:

IEnumerable<char> query = "Not what you might expect";
query = query.Where (c => c != 'a');
query = query.Where (c => c != 'e');
query = query.Where (c => c != 'i');
query = query.Where (c => c != 'o');
query = query.Where (c => c != 'u');

foreach (char c in query) Console.Write (c); //输出会是这样: Nt wht y mght xpct

现在我们重构了一下这个for 循环,看一看能发生什么。

IEnumerable<char> query = "Not what you might expect";
string vowels = "aeiou";
for (int i = 0; i < vowels.Length; i++)
​    query = query.Where (c => c != vowels[i]);

foreach (char c in query) Console.Write (c); //输出会是这样: Not what yo might expect

 

解决办法是,定义一个临时变量,每次for 循环时使用的都是新的值:

for (int i = 0; i < vowels.Length; i++)
{
​    char vowel = vowels[i];
​    query = query.Where (c => c != vowel);
}

 

How Deferred Execution Works(延时执行是怎么工作的呢?)

看一下书上是怎么说的

Query operators provide deferred execution by returning decorator sequences。

a decorator sequence (in general) has no backing structure of its own to store elements。 Instead, it wraps another sequence that you supply at runtime, to which it maintains a permanent dependency. Whenever you request data from a decorator, it in turn must request data from the wrapped input sequence.

查询操作符通过返回修饰符序列来提供延迟执行。

装饰器序列(通常)没有自己的底层结构来存储元素,相反,它封装了另一个你在运行时支持的序列,并对它保持永久依赖,无论你什么时候从装饰器中请求数据,它必须从封装的输入数据返回。

那啥是装饰器呢?

  • 这个装饰器就是像 where 、select 这样的东西。

前面说到LINQ 就像一个生产流水线,那where 和 select 这样的操作符就是生产线上的一个个环节,你写的LINQ 语句是在搭建这个生产线,在你执行 ToArray, ToList, ToDictionary, ToLookup 这些操作符时,就可以在生产线的终端获得处理过的数据。

如果输出序列没有执行转换,那么它将是一个代理而不是装饰器。

你还可以写一个自己的操作符,用C# 的迭代器来实现装饰器序列(还有Func 委托,扩展函数),下面是如何编写自己的Select方法(参数验证除外):

public static IEnumerable<TResult> Select<TSource,TResult>
(this IEnumerable<TSource> source, Func<TSource,TResult> selector)
{
	foreach (TSource element in source)
	yield return selector (element);
}

因此,当您调用Select或Where等操作符时,所做的只是实例化一个修饰输入序列的可枚举类。

 

Chaining Decorators

链接查询操作符创建装饰器层。

看下面的例子:

IEnumerable<int> query = new int[] { 5, 12, 3 }.Where (n => n < 10)
​                                                .OrderBy (n => n)
​                                                .Select (n => n * 10);

 

每个查询操作符实例化一个新的装饰包装前面的序列,(如同一个俄罗斯嵌套玩偶)。

 

当您枚举查询时,您是在查询通过分层或装饰器链转换的原始数组。(本质好似是一个委托链,)

 

 

一个查询的执行过程如下图所示:

 LINQ follows a demand-driven pull model, rather than a supply-driven push model. This is important—as we’ll see later —in allowing LINQ to scale to querying SQL databases.

//LINQ遵循需求驱动的拉模型,而不是供应驱动的推模型。

 

LINQ 的内容还有很多,这里只是刚刚介绍了: 变量的有效范围、延时执行 和延时执行的原理。后面还有子查询、组合策略、投射策略、解释查询 和 LINQ to SQL and Entity Framework 等等。建议大家看原书《 C#7.0 in a nutshell》。

posted on 2019-09-16 19:27  拾掇的往昔  阅读(890)  评论(0编辑  收藏  举报

导航