4.4 查詢語(yǔ)法(Query Syntax)
C# 中現(xiàn)有的 foreach 語(yǔ)句在 .NET Framework 的 IEnumerable/IEnumerator 方法之上提供了一個(gè)聲明式的語(yǔ)法(declarative syntax)。Foreach 語(yǔ)句確實(shí)是可選的(is strictly optional),但是卻被證明(proven to)是一個(gè)非常方便(convenient)和流行的(popular)語(yǔ)言機(jī)制(language mechanism)。
建立在這種先例之上(Building on this precedent),查詢語(yǔ)法(query syntax)對(duì)大多數(shù)通用的查詢操作符(common query operators)來說使用一個(gè)聲明式的語(yǔ)法(declarative syntax)來簡(jiǎn)單地查詢表達(dá)式(query expressions),如 Where, Select, SelectMany, GroupBy, OrderBy, ThenBy, OrderByDescending, 和ThenByDescending。
讓我們先看看下面這段本文開始就提到的簡(jiǎn)單的查詢:
IEnumerable<string> expr = names
.Where(s => s.Length == 5)
.OrderBy(s => s)
.Select(s => s.ToUpper());

使用查詢語(yǔ)法我們可以重寫這段原樣的語(yǔ)句(this exact statement)如下所示:
IEnumerable<string> expr = from s in names
where s.Length == 5
orderby s
select s.ToUpper();

就像 C# 的 foreach 語(yǔ)句一樣,查詢語(yǔ)法表達(dá)式(query syntax expressions)閱讀起來更緊湊更容易(more compact and easier),但是卻是完全隨意的(completely optional)。能寫進(jìn)查詢語(yǔ)法(query syntax)的每一個(gè)表達(dá)式都有一個(gè)相應(yīng)的(corresponding)使用句點(diǎn)“.”符號(hào)(using dot notation)的(雖然是更冗長(zhǎng)的(albeit more verbose))語(yǔ)法。
讓我們開始看看一個(gè)查詢表達(dá)式(query expression)的基本結(jié)構(gòu)(basic structure)。C# 里每一個(gè)合成的查詢表達(dá)式(syntactic query expression)都是從一個(gè) from 子句(from clause)開始,到一個(gè) select 或 group 子句結(jié)束。這個(gè)最初的(initial)from 子句能夠跟隨在(followed by)空的或多個(gè)(zero or more) from 或 where 子句后面。每個(gè) from 子句都是一個(gè)發(fā)生器(generator)來傳入(introduces)一個(gè)涉及(ranging over)一個(gè)序列(a sequence)的迭代變量(an iteration variable),而每一個(gè) where 子句是一個(gè)過濾器(filter)來排斥(excludes)結(jié)果中條目(items from the result)。最后的 select 或 group 子句都可以加上一個(gè) orderby 子句的前綴(be preceded by)用來指定結(jié)果集的排序(specifies an ordering for the result)。這種簡(jiǎn)單的語(yǔ)法(simplified grammar)對(duì)一個(gè)單個(gè)的查詢表達(dá)式(a single query expression)來說如下所示:
from itemName in srcExpr
((from itemName in srcExpr) | (where predExpr))*
(orderby (keyExpr (ascending|descending)?)+)?
((select selExpr) | (group selExpr by keyExpr))

舉例來說,考察下面兩段查詢表達(dá)式:
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;

編譯器對(duì)待(treats)這些查詢表達(dá)式就像如下它們用清楚的句點(diǎn)符號(hào)(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
});

查詢表達(dá)式(Query expressions)執(zhí)行了(perform)一個(gè)基于方法名(method names)的機(jī)器翻譯處理(mechanical translation)。被選擇的(that is chosen)精確的查詢操作符的實(shí)現(xiàn)(exact query operator implementation)既依靠(depends both on)被查詢的變量的類型又依靠活動(dòng)范圍(in scope)里的擴(kuò)展方法(extension methods)。
查詢表達(dá)式展示了多么遙遠(yuǎn)的(shown so far)未來,僅僅使用了一個(gè)發(fā)生器(only used one generator)。當(dāng)不止一個(gè)的發(fā)生器(generator)被使用的時(shí)候,每一個(gè)并發(fā)的發(fā)生器(each subsequent generator)在被它替代的事物的上下文(the context of its predecessor)中被賦值(evaluated)。舉例來說,考察這段對(duì)我們的查詢做了很下修改(slight modification)的程序:
var query = from s1 in names where s1.Length == 5
from s2 in names where s1 == s2
select s1 + " " + s2;

當(dāng)對(duì)下面的輸入的數(shù)組運(yùn)行時(shí):

string[] names =
{ "Burke", "Connor", "Frank", "Everett",
"Albert", "George", "Harris", "David" };

我們將得到下面的結(jié)果:
Burke Burke
Frank Frank
David David

上面這個(gè)查詢表達(dá)式用句點(diǎn)符號(hào)的表達(dá)式(dot notation expression)展開(expands)如下:
var query = names.Where(s1 => s1.Length == 5)
.SelectMany(s1 =>
names.Where(s2 => s1 == s2)
.Select(s2 => s1 + " " + s2)
);

注意 SelectMany 的使用會(huì)導(dǎo)致(causes)在我們外部的結(jié)果(in the outer result)中的內(nèi)部的查詢表達(dá)式(the inner query expression)變得呆板(to be flattened)。
我們對(duì)查詢表達(dá)式的簡(jiǎn)單的語(yǔ)法(simplified grammar)從本節(jié)開始(from earlier in this section)就忽略了(omitted)一個(gè)很有用的特性(very useful feature)。在一個(gè)并發(fā)的查詢里(in a subsequent query)它是很有用的在將一個(gè)查詢的結(jié)果(results of one query)視為(treat as)一個(gè)發(fā)生器(a generator)的時(shí)候。為了支持這種特性,查詢表達(dá)式使用 into 關(guān)鍵詞來在一個(gè) select 或 group 子句之后結(jié)合(splice)一個(gè)新的查詢表達(dá)式。這里是這種簡(jiǎn)單的語(yǔ)法來闡明(illustrates)into 關(guān)鍵詞是怎樣適應(yīng)(fits in with)其余的語(yǔ)法的(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))
)*

這個(gè) into 關(guān)鍵詞對(duì)后期處理(post-processing)一個(gè) group by 子句的結(jié)果來說是特別有用的(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);
}

這段程序輸出下面的結(jié)果:
Strings of length 7
Everett
Strings of length 6
Albert
Connor
George
Harris
Strings of length 5
Burke
David
Frank

本章節(jié)描述的是 C# 語(yǔ)言是怎么實(shí)現(xiàn)查詢表達(dá)式的,其他的語(yǔ)言可能選擇(elect)使用清楚的語(yǔ)法(explicit syntax)來支持附加的查詢操作符(additional query operators)。
需要重點(diǎn)注意的是(It is important to note that)查詢語(yǔ)法(query syntax)決不是(is by no means)硬要聯(lián)系上(hard-wired to)標(biāo)準(zhǔn)查詢操作符(the standard query operators),它是純凈的語(yǔ)法特性(purely syntactic feature)可以應(yīng)用于(applies to)任何通過使用適當(dāng)?shù)拿趾秃灻?/SPAN>the appropriate names and signatures)實(shí)現(xiàn)基本方法(underlying methods)來履行(fulfills)LINQ模式(LINQ pattern)的任何事物。上面描述的標(biāo)準(zhǔn)查詢操作符工作的方式是通過使用擴(kuò)展方法(extension methods)來增加(augment) IEnumerable<T> 接口。開發(fā)者可以使用(exploit)查詢語(yǔ)法(query syntax)在任何他們希望的類型上(any type they wish),只要(as long as)他們確信(make sure)它追隨(adheres to)LINQ模式(LINQ pattern),而不是通過直接實(shí)現(xiàn)需要的方法(direct implementation of the necessary methods),或者通過像擴(kuò)展方法一樣添加它們(adding them as extension methods)。
這個(gè)LINQ項(xiàng)目自己開發(fā)(exploited)的擴(kuò)展特性(extensibility)是通過供應(yīng)(the provision of)兩個(gè)LINQ式的API(two LINQ-enabled API's)提供的。其中一個(gè)名叫 DLinq,它是為基于SQL的數(shù)據(jù)訪問(SQL-based data access)提供的LINQ模式的實(shí)現(xiàn),另一個(gè)叫作 XLinq,它允許 LINQ 查詢 XML 數(shù)據(jù)。它們兩個(gè)都在下面的章節(jié)里描述。
待續(xù), 錯(cuò)誤難免,請(qǐng)批評(píng)指正,譯者Naven 2005-10-24