4.4 查询语法(Query Syntax)
C# 中现有的 foreach 语句在 .NET Framework 的 IEnumerable/IEnumerator 方法之上提供了一个声明式的语法(declarative syntax)。Foreach 语句确实是可选的(is strictly optional),但是却被证明(proven to)是一个非常方便(convenient)和流行的(popular)语言机制(language mechanism)。
建立在这种先例之上(Building on this precedent),查询语法(query syntax)对大多数通用的查询操作符(common query operators)来说使用一个声明式的语法(declarative syntax)来简单地查询表达式(query expressions),如 Where, Select, SelectMany, GroupBy, OrderBy, ThenBy, OrderByDescending, 和ThenByDescending。
让我们先看看下面这段本文开始就提到的简单的查询:
IEnumerable<string> expr = names
.Where(s => s.Length == 5)
.OrderBy(s => s)
.Select(s => s.ToUpper());
使用查询语法我们可以重写这段原样的语句(this exact statement)如下所示:
IEnumerable<string> expr = from s in names
where s.Length == 5
orderby s
select s.ToUpper();
就像 C# 的 foreach 语句一样,查询语法表达式(query syntax expressions)阅读起来更紧凑更容易(more compact and easier),但是却是完全随意的(completely optional)。能写进查询语法(query syntax)的每一个表达式都有一个相应的(corresponding)使用句点“.”符号(using dot notation)的(虽然是更冗长的(albeit more verbose))语法。
让我们开始看看一个查询表达式(query expression)的基本结构(basic structure)。C# 里每一个合成的查询表达式(syntactic query expression)都是从一个 from 子句(from clause)开始,到一个 select 或 group 子句结束。这个最初的(initial)from 子句能够跟随在(followed by)空的或多个(zero or more) from 或 where 子句后面。每个 from 子句都是一个发生器(generator)来传入(introduces)一个涉及(ranging over)一个序列(a sequence)的迭代变量(an iteration variable),而每一个 where 子句是一个过滤器(filter)来排斥(excludes)结果中条目(items from the result)。最后的 select 或 group 子句都可以加上一个 orderby 子句的前缀(be preceded by)用来指定结果集的排序(specifies an ordering for the result)。这种简单的语法(simplified grammar)对一个单个的查询表达式(a single query expression)来说如下所示:
from itemName in srcExpr
((from itemName in srcExpr) | (where predExpr))*
(orderby (keyExpr (ascending|descending)?)+)?
((select selExpr) | (group selExpr by keyExpr))
举例来说,考察下面两段查询表达式:
var query1 = from p in people
where p.Age > 20
orderby p.Age descending, p.Name
select new {
p.Name, Senior = p.Age > 30, p.CanCode
};
var query2 = from p in people
where p.Age > 20
orderby p.Age descending, p.Name
group new {
p.Name, Senior = p.Age > 30, p.CanCode
} by p.CanCode;
编译器对待(treats)这些查询表达式就像如下它们用清楚的句点符号(the following explicit dot-notation)写的程序一样:
var query1 = people.Where(p => p.Age > 20)
.OrderByDescending(p => p.Age)
.ThenBy(p => p.Name)
.Select(p => new {
p.Name,
Senior = p.Age > 30,
p.CanCode
});
var query2 = people.Where(p => p.Age > 20)
.OrderByDescending(p => p.Age)
.ThenBy(p => p.Name)
.GroupBy(p => p.CanCode,
p => new {
p.Name,
Senior = p.Age > 30,
p.CanCode
});
查询表达式(Query expressions)执行了(perform)一个基于方法名(method names)的机器翻译处理(mechanical translation)。被选择的(that is chosen)精确的查询操作符的实现(exact query operator implementation)既依靠(depends both on)被查询的变量的类型又依靠活动范围(in scope)里的扩展方法(extension methods)。
查询表达式展示了多么遥远的(shown so far)未来,仅仅使用了一个发生器(only used one generator)。当不止一个的发生器(generator)被使用的时候,每一个并发的发生器(each subsequent generator)在被它替代的事物的上下文(the context of its predecessor)中被赋值(evaluated)。举例来说,考察这段对我们的查询做了很下修改(slight modification)的程序:
var query = from s1 in names where s1.Length == 5
from s2 in names where s1 == s2
select s1 + " " + s2;
当对下面的输入的数组运行时:
string[] names = { "Burke", "Connor", "Frank", "Everett",
"Albert", "George", "Harris", "David" };
我们将得到下面的结果:
Burke Burke
Frank Frank
David David
上面这个查询表达式用句点符号的表达式(dot notation expression)展开(expands)如下:
var query = names.Where(s1 => s1.Length == 5)
.SelectMany(s1 =>
names.Where(s2 => s1 == s2)
.Select(s2 => s1 + " " + s2)
);
注意 SelectMany 的使用会导致(causes)在我们外部的结果(in the outer result)中的内部的查询表达式(the inner query expression)变得呆板(to be flattened)。
我们对查询表达式的简单的语法(simplified grammar)从本节开始(from earlier in this section)就忽略了(omitted)一个很有用的特性(very useful feature)。在一个并发的查询里(in a subsequent query)它是很有用的在将一个查询的结果(results of one query)视为(treat as)一个发生器(a generator)的时候。为了支持这种特性,查询表达式使用 into 关键词来在一个 select 或 group 子句之后结合(splice)一个新的查询表达式。这里是这种简单的语法来阐明(illustrates)into 关键词是怎样适应(fits in with)其余的语法的(the rest of the syntax)。
from itemName in srcExpr
((from itemName in srcExpr) | (where predExpr))*
(orderby (keyExpr (ascending|descending)?)+)?
((select selExpr) | (group selExpr by keyExpr))
(
into itemName
((from itemName in srcExpr) | (where predExpr))*
(orderby (keyExpr (ascending|descending)?)+)?
((select selExpr) | (group selExpr by keyExpr))
)*
这个 into 关键词对后期处理(post-processing)一个 group by 子句的结果来说是特别有用的(especially useful)。例如,考查下面的程序:
var query = from item in names
orderby item
group item by item.Length into lengthGroups
orderby lengthGroups.Key descending
select lengthGroups;
foreach (var group in query) {
Console.WriteLine("Strings of length {0}", group.Key);
foreach (var val in group.Group)
Console.WriteLine(" {0}", val);
}
这段程序输出下面的结果:
Strings of length 7
Everett
Strings of length 6
Albert
Connor
George
Harris
Strings of length 5
Burke
David
Frank
本章节描述的是 C# 语言是怎么实现查询表达式的,其他的语言可能选择(elect)使用清楚的语法(explicit syntax)来支持附加的查询操作符(additional query operators)。
需要重点注意的是(It is important to note that)查询语法(query syntax)决不是(is by no means)硬要联系上(hard-wired to)标准查询操作符(the standard query operators),它是纯净的语法特性(purely syntactic feature)可以应用于(applies to)任何通过使用适当的名字和签名(the appropriate names and signatures)实现基本方法(underlying methods)来履行(fulfills)LINQ模式(LINQ pattern)的任何事物。上面描述的标准查询操作符工作的方式是通过使用扩展方法(extension methods)来增加(augment) IEnumerable<T> 接口。开发者可以使用(exploit)查询语法(query syntax)在任何他们希望的类型上(any type they wish),只要(as long as)他们确信(make sure)它追随(adheres to)LINQ模式(LINQ pattern),而不是通过直接实现需要的方法(direct implementation of the necessary methods),或者通过像扩展方法一样添加它们(adding them as extension methods)。
这个LINQ项目自己开发(exploited)的扩展特性(extensibility)是通过供应(the provision of)两个LINQ式的API(two LINQ-enabled API's)提供的。其中一个名叫 DLinq,它是为基于SQL的数据访问(SQL-based data access)提供的LINQ模式的实现,另一个叫作 XLinq,它允许 LINQ 查询 XML 数据。它们两个都在下面的章节里描述。
待续, 错误难免,请批评指正,译者Naven 2005-10-24