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