1. 查詢操作鏈
為了構造更加復雜的查詢, 我們可以增加而外的查詢操作方法, 形成一個查詢鏈. 例如下面的例子:
1: string[] names = { "James","Jack","Harris"};
2:
3: IEnumerable<string> query = names.Where(n => n.Contains ("a")).OrderBy (n => n.Length).Select (n => n.ToUpper( ));
4:
5: foreach (string name in query)
6: Console.Write (name + ","); //JACK,JAMES,HARRIS,
Where, OrderBy和Select都是標准的查詢操作並且對應於Enumerable類中的擴展方法. Where會生成一個輸入序列的過濾版本, OrderBy則生成一個輸入序列的排序版本, Select方法生成一個新的序列, 此序列中的每個元素都是根據給定的lambda表達式進行了轉換或者返回. 整個數據流程是從左到右, 因此數據將先被過濾, 然後排序, 最後才被返回.
一個查詢操作不會去改變輸入序列, 相反的, 它總是返回一個新的序列. 這點上LINQ受到了函數式編程的影響, 並且保持一致的風格.
當查詢操作被串聯在一起的時候, 每一個操作生成的輸出序列將會被作為下一個操作的輸入序列. 因此我們也可以使用另外一種漸進式的查詢寫法:
1: var filtered = names.Where(n => n.Contains ("a"));
2: var sorted = filtered.OrderBy(n => n.Length);
3: var finalQuery = sorted.Select(n => n.ToUpper());
最終我們便利finalQuery得到的結果將會跟上面的例子是一樣的:
1: foreach (string name in finalQuery)
2: Console.Write (name + ","); // JACK,JAMES,HARRIS,
2. 編寫Lambda表達式
讓我們看一下一個最簡單的Lambda表達式,n => n.Contains(“a” ) , 在這個表達式中,輸入類型是string, 輸出類型是bool. 如果一個Lambda表達式返回一個bool值的話我們將它稱為一個判定(predicate). Lambda表達式的目的是取決於特定的查詢操作符. 例如如果是Where操作符的話, 它指示一個元素是否將會被包含在輸出序列中. 而如果是OrderBy操作符的話, Lambda表達式將會把輸入序列中的每一個元素映射到它的排序鍵上. 如果是Select操作符, Lambda表達式將決定輸入序列中的每個元素在被放入輸出序列之前應該如何轉換. 在一個操作中包含的Lambda表達式總是應用於輸入序列中的每一個單一元素, 而不是整個序列.
由於Lambda表達式是一個callback, 我們可以在其中放入我們自己的邏輯, 這令查詢操作非常的強大, 並且簡單. 我們可以看一下Enumerable.Where的實現, 忽略異常處理:
1: public static IEnumerable<TSource> Where<TSource> (
2: this IEnumerable<TSource> source,
3: Func<TSource,bool> predicate)
4: {
5: foreach (TSource element in source)
6: if (predicate (element))
7: yield return element;
8: }
2.1 Lambda表達式和Func標記
在標准的查詢操作符中利用了范型Func代理, 它們存在於System.Linq中, 並且定義如下
Func中出現的參數順序類型跟Lambda表達式中出現的一致. 因此, Func<TSource,bool>匹配一個TSource => bool的Lambda表達式, 它接受一個TSource類型的參數並且返回一個布爾值. 類似的, Func<TSource, TResult>匹配一個TSource => TResult的Lambda表達式. 以下是所有的Func代理定義(注意返回值的類型總是最後一個參數的類型):
delegate TResult Func <T> ( );
delegate TResult Func <T, TResult> (T arg1);
delegate TResult Func <T1, T2, TResult> (T1 arg1, T2 arg2);
delegate TResult Func <T1, T2, T3, TResult> (T1 arg1, T2 arg2, T3 arg3);
delegate TResult Func <T1, T2, T3, T4, TResult> (T1 arg1, T2 arg2, T3 arg3, T4 arg4);
2.2 Lambda表達式和元素命名
標准的查詢操作使用以下的類型命名:
TSource: 輸入序列中的元素類型
TResult: 輸出序列中的元素類型 – 如果它是不同於TSource的話
TKey: 用於sorting, grouping, join中的key類型
TSource是由輸入序列決定的, 而TResult和Tkey則由Lambda表示式來推斷. 例如, 考慮以下的Select操作:
1: static IEnumerable<TResult> Select<TSource,TResult> (
2: this IEnumerable<TSource> source,
3: Func<TSource,TResult> selector)
Func<TSource,TResult>匹配一個TSource=>TResult的Lambda表達式, 一個對應於輸入元素, 另一個則對應於輸出元素. TSource和TResult是不同的類型, 因此Lambda表達式可以更改每一個元素的類型. 更進一步, Lambda表達式決定了輸出序列的類型. 例如:
1: string[] names = { "James","Jack","Harris"};
2: IEnumerable<int> query = names.Select (n => n.Length);
3: foreach (int length in query)
4: Console.Write (length); // 546
3. 自然排序
針對一個輸入序列的排序功能在LINQ中是非常重要的. 一些操作符, 例如Take, Skip和Reverse都依賴於這個行為. Take操作符輸出頭x個元素, 而排除剩余的, Skip操作符則忽略了頭x個元素, 輸出所有余下的. Reverse操作符將一個序列中的元素進行反轉.
4. 其他操作符
並非所有的操作都回返回一個序列, 針對於元素的操作符都只返回一個元素, 例如First, Last, Single, ElementAt等等:
1: int[] myNumbers = { 7,6,5,4,3};
2: int fn = myNumbers.First(); // 7
3: Console.WriteLine(fn);
4: int ln = myNumbers.Last(); // 3
5: Console.WriteLine(ln);
6: int sn = myNumbers.ElementAt(1); // 6
7: Console.WriteLine(sn);
另外聚合操作符則返回一個單一值:
1: int count = myNumbers.Count(); // 5;
2: Console.WriteLine(count);
3: int min = myNumbers.Min(); // 3;
4: Console.WriteLine(min);
另外Contains,Any則返回bool值. 這些操作符並不返回一個集合, 因此他們不能在他們的結果集中再次調用其它的操作, 換句話說他們必須出現在整個查詢鏈中的最後一個(或者是子查詢.) 另外有些操作符接受兩個輸入序列, 例如Contact, 它將會將一個序列添加到另外一個當中去, 而Union類似, 區別是它會自動刪除其中重復的元素. 待續!