C#學習筆記(八)—–LINQ查詢的基礎知識(上)

由於都是手打的字,難免會出現錯誤,請大家指正,感激。比如下面出現的LINQ、LInq、LINq等都是指的一個東西:LINQ


LINQ查詢

  • lINQ是Language Integrated Query的簡稱,他可以視爲一組語言和框架的特性的集合,我們可以使用LINQ對本地對象和遠程數據源進行結構化的類型安全的檢查。LINQ在C# 3.0 /Framework 3.5時引入。
  • LINQ用於查詢任何實現了IEnumerable<T>的集合類型,數組啊,list啊,遠程的數據源啊等等,LINQ具有編譯時類型檢查和動態查詢組合這兩大優點。
  • LINQ中所有核心元素都是在System.Linq和System.Linq.Expressions中。

入門

  • LINQ數據源的基本組成部分是序列和元素,序列是指任何實現了IEnumerable<T>的對象,元素是指這個序列中的每一個成員。像下面這個names:
    string[] names = { "Tom", "Dick", "Harry" };names是序列也是內存中的一個對象集合,我們也可以稱之爲本地序列,其中的Tom、Dick等是元素。
  • 查詢運算符用於轉換序列,通常,一個查詢運算符接收一個輸入的序列,並經過轉換爲一個輸出序列,在System.Linq中定義了約40中運算符,這些運算符都是靜態的擴展方法,成爲標準查詢運算符。
  • 提示我們把對本地序列進行的查詢叫做本地查詢或LInqToObject查詢。Linq還支持對那些從遠程數據源動態獲取的序列進行查詢。這些序列需要實現IQueryble<T>接口,而在Queryble<T>類中則有一組相應的標準查詢運算符進行支持,一個查詢可以理解爲使用一個查詢運算符對一個序列進行轉換的表達式。最簡單的查詢包含一個查詢運算符和一個輸入序列。例如我們使用where運算符:
string[] names = { "Tom", "Dick", "Harry" };
IEnumerable<string> filteredNames = System.Linq.Enumerable.Where
(names, n => n.Length >= 4);
foreach (string n in filteredNames)
Console.WriteLine (n);
輸出:
Dick
Harry

因爲查詢運算符都是擴展方法,所以我們可以像調用實例方法那樣在names上面調用where:

IEnumerable<string> filteredNames = names.Where (n => n.Length >= 4);

想要這些通過編譯,你必須導入System.Linq命名空間,下面這個完整的例子:

using System;
usign System.Collections.Generic;
using System.Linq;
class LinqDemo
{
static void Main()
{
string[] names = { "Tom", "Dick", "Harry" };
IEnumerable<string> filteredNames = names.Where (n => n.Length >= 4);
foreach (string name in filteredNames) Console.WriteLine (name);
}
}
Dick
Harry

提示:實際上我們也可以通過var來定義查詢的結果集進一步精簡代碼,但是這樣會降低可讀性,並且在vs以外的IDE中也不會得到這種智能提示。
大多數查詢運算符都接受一個lambda表達式作爲參數,lambda表達式用於對查詢進行格式化。本例中的lambda表達式如下:n=>n.Length>=4這裏的輸入參數對應一個輸入元素,辦理中,輸入參數n序列中的一個元素,可以看出是string類型的。Where查詢運算符要求這個lambda表達式返回一個bool值,如果返回true,那麼表示這個元素應該包含在返回的序列裏。下面是Where運算符的簽名:

public static IEnumerable<TSource> Where<TSource>
(this IEnumerable<TSource> source, Func<TSource,bool> predicate)

下面的查詢要找出序列中所有包含字母a的單詞的集合:

IEnumerable<string> filteredNames = names.Where (n => n.Contains ("a"));
foreach (string name in filteredNames)
Console.WriteLine (name); // Harry

到目前爲止,我們使用了標準查詢運算符和lambda表達式進行編寫的Linq查詢語句,這種查詢方式是可以連續拼接起來使用的,我們稱這種方法爲運算符流語法。C#還提供了另外一種語法來編寫linq語句 ,叫做查詢表達式語法,使用這種查詢表達式,可以將前面的語句編寫成:

IEnumerable<string> filteredNames = from n in names
where n.Contains ("a")
select n;

這兩種語法是互補的,在下面的文字中,我們會詳細的介紹每一種用法。

運算符流語法

運算符流語法是最基本的查詢語法,我們後續還會介紹連續使用多個查詢運算符進行查詢的語句,從中我們可以看到擴展方法的強大功能。此外,還會介紹lambda表達式的書寫規則和幾個新的查詢運算符。

連續使用查詢運算符

前面的介紹使用的都是一個查詢運算符,這次使用多個:

using System;
using System.Collections.Generic;
using System.Linq;
class LinqDemo
{
static void Main()
{
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);
}
}
JAY
MARY
HARRY

這裏先用Where將序列中帶a的名字都拿出來,再把新的序列中的元素按照長度排序,最後將這些名字都轉化成大寫後再輸出。
需要注意的是,上面的每個查詢運算符中的n都是各自獨立的,私有的。可以用下面的示例說明這三個n的關係:

void Test()
{
foreach (char c in "string1") Console.Write (c);
foreach (char c in "string2") Console.Write (c);
foreach (char c in "string3") Console.Write (c);
}

Where、OrderBy、Select都是標準查詢運算符,對應System.Linq命名空間中的三個擴展方法。Where查詢運算符的作用是篩選傳進來的序列,OrderBy是都傳進來的序列進行排序,而Select的作用是投影,將參數內的lambda表達式的參數n進行一個提取,甚至可以生成一個和之前序列毫無關係的對象,一句話總結:Where對序列進行的是縱向的縮小(把沒用的行都扔了),Select對序列進行的是橫向的縮小(把元素的一些沒用的字段都扔了)。特別需要注意的是查詢運算符只是生成了一個新的序列,並不會改變原有的序列,這種設計師符合函數式編程規範的,LINQ的思想實際上起源於函數式編程。
下面是這三種運算符多對應的擴展方法:

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)

當我們以上面的實例的方式連續使用三個查詢運算符進行查詢時,可以將各個運算符的處理過程理解成一個傳送帶,從中可以更直觀的看出每一個運算符的輸入及輸出:
這裏寫圖片描述
當然我們也可以把他們分別弄成一個語句,而不是把他們放在一個語句中:

// You must import the System.Linq namespace for this to compile:
IEnumerable<string> filtered = names .Where (n => n.Contains ("a"));
IEnumerable<string> sorted = filtered.OrderBy (n => n.Length);
IEnumerable<string> finalQuery = sorted .Select (n => n.ToUpper());

這裏的finalQuery得到的結果和上面的結果是一樣的。實際上這三個語句每一個都會輸出一個新的集合結果,例如:

foreach (string name in filtered)
Console.Write (name + "|"); // Harry|Mary|Jay|
Console.WriteLine();
foreach (string name in sorted)
Console.Write (name + "|"); // Jay|Mary|Harry|
Console.WriteLine();
foreach (string name in finalQuery)
Console.Write (name + "|"); // JAY|MARY|HARRY|

擴展方法的重要性

前面我們提到,每個查詢運算符對應着一個擴展方法,前面的示例中我們都是使用查詢運算符對集合進行操作,實際上,也可以直接使用這些擴展方法來完成查詢,例如:

IEnumerable<string> filtered = Enumerable.Where (names,
n => n.Contains ("a"));
IEnumerable<string> sorted = Enumerable.OrderBy (filtered, n => n.Length);
IEnumerable<string> finalQuery = Enumerable.Select (sorted,
n => n.ToUpper());

實際上這也是編譯器在遇到標準查詢運算符的處理方式,把它們編譯成對應函數進行調用,但是,這種調用方式是不可取的,舉個例子來說:如果我們想在一個LINQ表達式中使用多個運算符,可以這樣寫:

IEnumerable<string> query = names.Where (n => n.Contains ("a"))
.OrderBy (n => n.Length)
.Select (n => n.ToUpper());

這種寫法可以很清楚的表示出數據是從左邊到右邊一次處理的,但是如果直接調用Enumerable類中的方法來完成這個查詢,表達式就不那麼容易理解了:

IEnumerable<string> query =
Enumerable.Select (
Enumerable.OrderBy (
Enumerable.Where (
names, n => n.Contains ("a")
), n => n.Length
), n => n.ToUpper()
);

使用Lambda表達式

在前面的實例中,我們把如下Lambda表達式作爲參數傳給了Where運算符:

n => n.Contains ("a") // Input type=string, return type=bool.//返回一個bool值的表達式我們可以稱之爲斷言。

對於不同的查詢運算符來說,Lambda表達式的作用也不同。在Where運算符中,Lambda表達式用於判斷一個元素是否應該被包含在輸出序列中。也就是判斷元素是否符合篩選條件;在OrderBy運算符中,Lambda表達式用於將集合中的每個元素映射到他們的排序鍵上;在Select運算符中,Lambda表達式用於定義輸入序列以何種方式、格式進行輸出。需要注意的是,查詢運算符的Lambda表達式針對的是集合中的每個元素,而不是集合整體。
實際上,運算符會自動識別傳遞給他的Lambda表達式的意義,典型的情況是:Lambda表達式會作用於序列中的每個元素,並且在操作每個元素的時候都會都對Lambda表達式進行解析,這使得查詢運算符看起來很神奇,但它的底層實現是很容易理解的,一Enumerable類中的Where方法爲例:它的實現如下:

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;
}
發佈了47 篇原創文章 · 獲贊 8 · 訪問量 3萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章