LINQ之路 4:LINQ方法語(yǔ)法
書寫LINQ查詢時(shí)又兩種語(yǔ)法可供選擇:方法語(yǔ)法(Fluent Syntax)和查詢語(yǔ)法(Query Expression)。
LINQ方法語(yǔ)法是非常靈活和重要的,我們?cè)谶@里將描述使用鏈接查詢運(yùn)算符的方式來(lái)創(chuàng)建復(fù)雜的查詢,方法語(yǔ)法的本質(zhì)是通過(guò)擴(kuò)展方法和Lambda表達(dá)式來(lái)創(chuàng)建查詢。C# 3.0對(duì)于LINQ表達(dá)式還引入了聲明式的查詢語(yǔ)法,通過(guò)查詢語(yǔ)法寫出的查詢比較類似于SQL查詢。本篇會(huì)對(duì)LINQ方法語(yǔ)法進(jìn)行詳細(xì)的介紹。
當(dāng)然,.NET公共語(yǔ)言運(yùn)行庫(kù)(CLR)并不具有查詢語(yǔ)法的概念。所以,編譯器會(huì)在程序編譯時(shí)把查詢表達(dá)式轉(zhuǎn)換為方法語(yǔ)法,即對(duì)擴(kuò)展方法的調(diào)用。所以使用方法語(yǔ)法會(huì)讓我們更加接近和了解LINQ的實(shí)現(xiàn)和本質(zhì),并且一些查詢只能表示為方法調(diào)用,如檢索序列中的最大值、最小值元素的查詢,他們?cè)诓樵冋Z(yǔ)法中就沒(méi)有對(duì)應(yīng)的實(shí)現(xiàn)。但另一方面,查詢語(yǔ)法通常會(huì)比較簡(jiǎn)單和易讀。不管怎樣,這兩種語(yǔ)法和互相補(bǔ)充和兼容的,我們可以在一個(gè)查詢中混合使用方法語(yǔ)法和查詢語(yǔ)法。
鏈接查詢運(yùn)算符
在LINQ介紹中,我們示范了使用單個(gè)查詢運(yùn)算符創(chuàng)建的查詢。如果需要?jiǎng)?chuàng)建更加復(fù)雜的查詢,我們可以在表達(dá)式之后添加其他查詢運(yùn)算符,產(chǎn)生一個(gè)查詢鏈。如下例:查詢出所有含有字母”a”的姓名,按長(zhǎng)度進(jìn)行排序,然后把結(jié)果全部轉(zhuǎn)換成大寫格式。
static void Main(string[] args)
{
string[] names = { "Tom", "Dick", "Harry", "Mary", "Jay" };
IEnumerable<string> query = names
.Where(n => n.Contains("a"))
.OrderBy(n => n.Length)
.Select(n => n.ToUpper());
foreach (string name in query) Console.WriteLine(name);
}
// Result:
JAY
MARY
HARRY
就像在本例中所示,當(dāng)鏈接使用查詢運(yùn)算符時(shí),一個(gè)運(yùn)算符的輸出sequence會(huì)成為下一個(gè)運(yùn)算符的輸入sequence,其結(jié)果形成了一個(gè)sequence的傳輸鏈,如圖所示:

圖:鏈接查詢運(yùn)算符
Where, OrderyBy, Select這些標(biāo)準(zhǔn)查詢運(yùn)算符對(duì)應(yīng)Enumerable類中的相應(yīng)擴(kuò)展方法。Where產(chǎn)生一個(gè)經(jīng)過(guò)過(guò)濾的sequence;OrderBy生成輸入sequence的排序版本;Select得到的序列中的每個(gè)元素都經(jīng)過(guò)了給定lambda表達(dá)式的轉(zhuǎn)換。
下面是Where, OrderBy, Select這幾個(gè)擴(kuò)展方法的簽名:
public static IEnumerable<TSource> Where<TSource>
(this IEnumerable<TSource> source, Func<TSource, bool> predicate)
public static IEnumerable<TSource> OrderBy<TSource, TKey>
(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector)
public static IEnumerable<TResult> Select<TSource, TResult>
(this IEnumerable<TSource> source, Func<TSource, TResult> selector)
擴(kuò)展方法的重要性
在LINQ表達(dá)式中,正是擴(kuò)展方法讓LINQ查詢運(yùn)算符的鏈接成為了可能。因?yàn)檫\(yùn)算符本身是對(duì)IEnumerable<T>類型的擴(kuò)展,并且返回IEnumerable<T>類型的結(jié)果。我們可以比較一下使用擴(kuò)展方法和使用靜態(tài)方法的區(qū)別,結(jié)果會(huì)一目了然。擴(kuò)展方法非常自然地反映了從左到右的數(shù)據(jù)流向,同時(shí)保持lambda表達(dá)式與查詢運(yùn)算符的位置一致性。
// extension methods make LINQ elegant
IEnumerable<string> query = names
.Where(n => n.Contains("a"))
.OrderBy(n => n.Length)
.Select(n => n.ToUpper());
// static methods lose query's fluency
IEnumerable<string> query2 =
Enumerable.Select(
Enumerable.OrderBy(
Enumerable.Where(names, n => n.Contains("a")
), n => n.Length
), n => n.ToUpper()
);
創(chuàng)建Lambda表達(dá)式
在上例中,我們向Where運(yùn)算符提供了如下Lambda表達(dá)式:n => n.Contains(“a”)。對(duì)各個(gè)查詢運(yùn)算符來(lái)說(shuō),Lambda表達(dá)式的目的不盡相同。對(duì)于Where,它決定了一個(gè)element是否包含在結(jié)果sequence中;對(duì)于OrderBy,它把每個(gè)element映射到比較的鍵值;而對(duì)于Select,lambda表達(dá)式則決定了輸入sequence中的元素要怎么樣的轉(zhuǎn)換再放入輸出sequence中。
關(guān)于Lambda表達(dá)式的詳細(xì)介紹,請(qǐng)參考LINQ 之路3:C# 3.0的語(yǔ)言功能(下)。
通常來(lái)說(shuō),查詢運(yùn)算符會(huì)對(duì)每一個(gè)輸入sequence中的element來(lái)調(diào)用我們提供的Lambda表達(dá)式,這樣就給了我們一個(gè)實(shí)現(xiàn)自己的邏輯,從而得到自己需要結(jié)果的機(jī)會(huì)。我們可以看一下.NET Framework對(duì)Enumerable.Where的實(shí)現(xiàn):
public static IEnumerable<TSource> Where<TSource>(
this IEnumerable<TSource> source, Func<TSource, bool> predicate)
{
foreach (TSource element in source)
if (predicate(element))
yield return element;
}
標(biāo)準(zhǔn)查詢運(yùn)算符使用了通用的Func委托,F(xiàn)unc是一組定義在System.Linq(謝謝 A_明~堅(jiān)持的指正,此處應(yīng)為System)命名空間中的通用委托。它接受一系列的輸入?yún)?shù)和一個(gè)返回值,返回值對(duì)應(yīng)最后一個(gè)參數(shù)定義。所以,F(xiàn)unc<TSource, bool>委托匹配 TSource => bool表達(dá)式,接受TSource輸入?yún)?shù),返回一個(gè)bool值。
Lambda表達(dá)式和元素類型
標(biāo)準(zhǔn)查詢運(yùn)算符使用統(tǒng)一的類型名稱:
|
通用類型名稱 |
用途 |
|
TSource |
Input sequence中的element類型 |
|
TResult |
Output sequence中的element 類型(如果和TSource不相同) |
|
TKey |
使用sorting、grouping、joining等的鍵值類型 |
TSource由輸入sequence決定,而TResult和TKey則從我們提供的Lambda表達(dá)式推斷得到。
比如:Select查詢運(yùn)算符的簽名如下:
public static IEnumerable<TResult> Select<TSource,TResult>(
this IEnumerable<TSource> source, Func<TSource,TResult> selector)
Func<TSource,TResult>匹配TSource => TResult的Lambda表達(dá)式,接受一個(gè)輸入?yún)?shù)TSource,返回TResult。因?yàn)門Source和TResult是不同的類型,所以我們的Lambda表達(dá)式甚至可以改變輸入element的數(shù)據(jù)類型。下面的示例就把string類型元素轉(zhuǎn)換為int類型元素:
static void TestSelectOperator(){
string[] names = { "Tom", "Dick", "Harry", "Mary", "Jay" };
// 編譯器將會(huì)從Lambda表達(dá)式 n => n.Length推斷出TResult為int類型
IEnumerable<int> query = names.Select(n => n.Length);
foreach (int length in query)
Console.Write(length + "|"); // 3|4|5|4|3
}
而對(duì)Where查詢運(yùn)算符來(lái)講,它并不需要對(duì)輸出element進(jìn)行類型推斷,因?yàn)樗皇菍?duì)輸入elements進(jìn)行過(guò)濾而不作轉(zhuǎn)換,因此輸出element和輸入element具有相同的數(shù)據(jù)類型。
對(duì)于OrderBy查詢運(yùn)算符來(lái)講,F(xiàn)unc<TSource, TKey>把輸入元素映射至一個(gè)排序鍵值。TKey由Lambda表達(dá)式的結(jié)果推斷出來(lái),比如我們可以按長(zhǎng)度或按字母順序?qū)ames數(shù)組進(jìn)行排序:
string[] names = { "Tom", "Dick", "Harry", "Mary", "Jay" };
IEnumerable<string> sortedByLength, sortedAlphabetically;
sortedByLength = names.OrderBy(n => n.Length); // int key
sortedAlphabetically = names.OrderBy(n => n); // string key
其他查詢運(yùn)算符
并不是所有的查詢運(yùn)算符都返回一個(gè)sequence。元素(element)運(yùn)算符會(huì)從輸入sequence中獲取單個(gè)元素,如:First,Last和ElementAt:
int[] numbers = { 10, 9, 8, 7, 6 };
int firstNumber = numbers.First(); // 10
int lastNumber = numbers.Last(); // 6
int secondNumber = numbers.ElementAt(1); // 9
int lowestNumber = numbers.OrderBy(n => n).First(); // 6
集合(aggregation) 運(yùn)算符返回一個(gè)標(biāo)量值,通常是數(shù)值類型:
int count = numbers.Count(); // 5
int min = numbers.Min(); // 6
判斷運(yùn)算符返回一個(gè)bool值:
bool hasTheNumberNine = numbers.Contains(9); // true
bool hasElements = numbers.Any(); // true
bool hasAnOddElement = numbers.Any(n => (n % 2) == 1); //true
因?yàn)檫@些運(yùn)算符并不是返回一個(gè)sequence,所以我們不能再這些運(yùn)算符之后鏈接其他運(yùn)算符,換句話講,他們一般出現(xiàn)在查詢的最后面。
還有一些查詢運(yùn)算符接受兩個(gè)輸入sequence,比如Concat把一個(gè)sequence添加到另外一個(gè)sequence后面;Union與Concat類似,但是會(huì)去除相同的元素:
int[] seq1 = { 1, 2, 2, 3 };
int[] seq2 = { 3, 4, 5 };
IEnumerable<int> concat = seq1.Concat(seq2); // { 1, 2, 2, 3, 3, 4, 5 }
IEnumerable<int> union = seq1.Union(seq2); // { 1, 2, 3, 4, 5 }
本篇只是對(duì)幾個(gè)常用的查詢運(yùn)算符做了介紹,在后續(xù)篇章中,我會(huì)對(duì)更多地運(yùn)算符進(jìn)行更詳細(xì)的討論。

浙公網(wǎng)安備 33010602011771號(hào)